diff --git a/Docker/run/config/standalone/server-standalone.json b/Docker/run/config/standalone/server-standalone.json index 42812fd..1bc4aa5 100644 --- a/Docker/run/config/standalone/server-standalone.json +++ b/Docker/run/config/standalone/server-standalone.json @@ -7,9 +7,11 @@ "Default": "Warning", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", - "MareSynchronosServer": "Information", - "MareSynchronosShared": "Information", - "System.IO": "Information" + "Microsoft.EntityFramework": "Warning", + "MareSynchronosServer": "Debug", + "MareSynchronosShared": "Debug", + "System.IO": "Information", + "MareSynchronosServer.Services.SystemInfoService": "Warning" }, "File": { "BasePath": "logs", @@ -37,8 +39,8 @@ ], "RedisConnectionString": "redis,password=secretredispassword", "CdnFullUrl": "http://localhost:6200", - "MaxExistingGroupsByUser": 3, - "MaxJoinedGroupsByUser": 6, + "MaxExistingGroupsByUser": 6, + "MaxJoinedGroupsByUser": 10, "MaxGroupUserCount": 100, "PurgeUnusedAccounts": false, "PurgeUnusedAccountsPeriodInDays": 14, diff --git a/MareAPI b/MareAPI index 820a432..3c0f1e6 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 820a432ad9ac6c19f9908ec0f51671550e0fa151 +Subproject commit 3c0f1e6eba6b28d971afb521af94f92c1542ecb1 diff --git a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthReply.cs b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthReply.cs index 5ca2a01..aaeabbe 100644 --- a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthReply.cs +++ b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthReply.cs @@ -1,3 +1,3 @@ namespace MareSynchronosServer.Authentication; -public record SecretKeyAuthReply(bool Success, string Uid, bool TempBan, bool Permaban); +public record SecretKeyAuthReply(bool Success, string Uid, string PrimaryUid, bool TempBan, bool Permaban); diff --git a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticatorService.cs b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticatorService.cs index b9a0144..3d50093 100644 --- a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticatorService.cs +++ b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticatorService.cs @@ -50,24 +50,34 @@ public class SecretKeyAuthenticatorService _failedAuthorizations.Remove(ip, out _); }); } - return new(Success: false, Uid: null, TempBan: true, Permaban: false); + return new(Success: false, Uid: null, PrimaryUid: null, TempBan: true, Permaban: false); } using var scope = _serviceScopeFactory.CreateScope(); using var context = scope.ServiceProvider.GetService(); var authReply = await context.Auth.AsNoTracking().SingleOrDefaultAsync(u => u.HashedKey == hashedSecretKey).ConfigureAwait(false); + var isBanned = authReply?.IsBanned ?? false; + var primaryUid = authReply.PrimaryUserUID ?? authReply.UserUID; - SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID, false, authReply?.IsBanned ?? false); + if (authReply.PrimaryUserUID != null) + { + var primaryUser = await context.Auth.AsNoTracking().SingleOrDefaultAsync(u => u.UserUID == authReply.PrimaryUserUID).ConfigureAwait(false); + isBanned = isBanned || primaryUser.IsBanned; + } + + SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID, authReply.PrimaryUserUID ?? authReply.UserUID, TempBan: false, isBanned); if (reply.Success) { _metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses); + _metrics.IncGauge(MetricsAPI.GaugeAuthenticationCacheEntries); _cachedPositiveResponses[hashedSecretKey] = reply; _ = Task.Run(async () => { await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false); _cachedPositiveResponses.TryRemove(hashedSecretKey, out _); + _metrics.DecGauge(MetricsAPI.GaugeAuthenticationCacheEntries); }); } @@ -85,7 +95,7 @@ public class SecretKeyAuthenticatorService _logger.LogWarning("Failed authorization from {ip}", ip); var whitelisted = _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.WhitelistedIps), new List()); - if (!whitelisted.Any(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase))) + if (!whitelisted.Exists(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase))) { if (_failedAuthorizations.TryGetValue(ip, out var auth)) { @@ -97,6 +107,6 @@ public class SecretKeyAuthenticatorService } } - return new(Success: false, Uid: null, TempBan: false, Permaban: false); + return new(Success: false, Uid: null, PrimaryUid: null, TempBan: false, Permaban: false); } } diff --git a/MareSynchronosServer/MareSynchronosServer/Controllers/JwtController.cs b/MareSynchronosServer/MareSynchronosServer/Controllers/JwtController.cs index 2810617..feeac6c 100644 --- a/MareSynchronosServer/MareSynchronosServer/Controllers/JwtController.cs +++ b/MareSynchronosServer/MareSynchronosServer/Controllers/JwtController.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using StackExchange.Redis.Extensions.Core.Abstractions; +using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; @@ -21,10 +22,10 @@ namespace MareSynchronosServer.Controllers; public class JwtController : Controller { private readonly IHttpContextAccessor _accessor; - private readonly IRedisDatabase _redis; - private readonly MareDbContext _mareDbContext; - private readonly SecretKeyAuthenticatorService _secretKeyAuthenticatorService; private readonly IConfigurationService _configuration; + private readonly MareDbContext _mareDbContext; + private readonly IRedisDatabase _redis; + private readonly SecretKeyAuthenticatorService _secretKeyAuthenticatorService; public JwtController(IHttpContextAccessor accessor, MareDbContext mareDbContext, SecretKeyAuthenticatorService secretKeyAuthenticatorService, @@ -41,6 +42,33 @@ public class JwtController : Controller [AllowAnonymous] [HttpPost(MareAuth.Auth_CreateIdent)] public async Task CreateToken(string auth, string charaIdent) + { + return await AuthenticateInternal(auth, charaIdent).ConfigureAwait(false); + } + + [Authorize(Policy = "Authenticated")] + [HttpGet("renewToken")] + public async Task RenewToken() + { + var uid = HttpContext.User.Claims.Single(p => string.Equals(p.Type, MareClaimTypes.Uid, StringComparison.Ordinal))!.Value; + var ident = HttpContext.User.Claims.Single(p => string.Equals(p.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))!.Value; + + if (await _mareDbContext.Auth.Where(u => u.UserUID == uid || u.PrimaryUserUID == uid).AnyAsync(a => a.IsBanned)) + { + await EnsureBan(uid, ident); + + return Unauthorized("You are permanently banned."); + } + + if (await IsIdentBanned(uid, ident)) + { + return Unauthorized("Your character is banned from using the service."); + } + + return CreateJwtFromId(uid, ident); + } + + private async Task AuthenticateInternal(string auth, string charaIdent) { if (string.IsNullOrEmpty(auth)) return BadRequest("No Authkey"); if (string.IsNullOrEmpty(charaIdent)) return BadRequest("No CharaIdent"); @@ -49,16 +77,8 @@ public class JwtController : Controller var authResult = await _secretKeyAuthenticatorService.AuthorizeAsync(ip, auth); - var isBanned = await _mareDbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == charaIdent).ConfigureAwait(false); - if (isBanned) + if (await IsIdentBanned(authResult.Uid, charaIdent)) { - var authToBan = _mareDbContext.Auth.SingleOrDefault(a => a.UserUID == authResult.Uid); - if (authToBan != null) - { - authToBan.IsBanned = true; - await _mareDbContext.SaveChangesAsync().ConfigureAwait(false); - } - return Unauthorized("Your character is banned from using the service."); } @@ -66,38 +86,7 @@ public class JwtController : Controller if (!authResult.Success && authResult.TempBan) return Unauthorized("You are temporarily banned. Try connecting again in 5 minutes."); if (authResult.Permaban) { - if (!_mareDbContext.BannedUsers.Any(c => c.CharacterIdentification == charaIdent)) - { - _mareDbContext.BannedUsers.Add(new Banned() - { - CharacterIdentification = charaIdent, - Reason = "Autobanned CharacterIdent (" + authResult.Uid + ")", - }); - - await _mareDbContext.SaveChangesAsync(); - } - - var lodestone = await _mareDbContext.LodeStoneAuth.Include(a => a.User).FirstOrDefaultAsync(c => c.User.UID == authResult.Uid); - - if (lodestone != null) - { - if (!_mareDbContext.BannedRegistrations.Any(c => c.DiscordIdOrLodestoneAuth == lodestone.HashedLodestoneId)) - { - _mareDbContext.BannedRegistrations.Add(new BannedRegistrations() - { - DiscordIdOrLodestoneAuth = lodestone.HashedLodestoneId, - }); - } - if (!_mareDbContext.BannedRegistrations.Any(c => c.DiscordIdOrLodestoneAuth == lodestone.DiscordId.ToString())) - { - _mareDbContext.BannedRegistrations.Add(new BannedRegistrations() - { - DiscordIdOrLodestoneAuth = lodestone.DiscordId.ToString(), - }); - } - - await _mareDbContext.SaveChangesAsync(); - } + await EnsureBan(authResult.Uid, charaIdent); return Unauthorized("You are permanently banned."); } @@ -105,16 +94,10 @@ public class JwtController : Controller var existingIdent = await _redis.GetAsync("UID:" + authResult.Uid); if (!string.IsNullOrEmpty(existingIdent)) return Unauthorized("Already logged in to this account. Reconnect in 60 seconds. If you keep seeing this issue, restart your game."); - var token = CreateToken(new List() - { - new Claim(MareClaimTypes.Uid, authResult.Uid), - new Claim(MareClaimTypes.CharaIdent, charaIdent), - }); - - return Content(token.RawData); + return CreateJwtFromId(authResult.Uid, charaIdent); } - private JwtSecurityToken CreateToken(IEnumerable authClaims) + private JwtSecurityToken CreateJwt(IEnumerable authClaims) { var authSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration.GetValue(nameof(MareConfigurationAuthBase.Jwt)))); @@ -122,9 +105,78 @@ public class JwtController : Controller { Subject = new ClaimsIdentity(authClaims), SigningCredentials = new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256Signature), + Expires = new(long.Parse(authClaims.First(f => string.Equals(f.Type, MareClaimTypes.Expires, StringComparison.Ordinal)).Value!, CultureInfo.InvariantCulture), DateTimeKind.Utc), }; var handler = new JwtSecurityTokenHandler(); return handler.CreateJwtSecurityToken(token); } -} + + private IActionResult CreateJwtFromId(string uid, string charaIdent) + { + var token = CreateJwt(new List() + { + new Claim(MareClaimTypes.Uid, uid), + new Claim(MareClaimTypes.CharaIdent, charaIdent), + new Claim(MareClaimTypes.Expires, DateTime.UtcNow.AddHours(6).Ticks.ToString(CultureInfo.InvariantCulture)) + }); + + return Content(token.RawData); + } + + private async Task EnsureBan(string uid, string charaIdent) + { + if (!_mareDbContext.BannedUsers.Any(c => c.CharacterIdentification == charaIdent)) + { + _mareDbContext.BannedUsers.Add(new Banned() + { + CharacterIdentification = charaIdent, + Reason = "Autobanned CharacterIdent (" + uid + ")", + }); + + await _mareDbContext.SaveChangesAsync(); + } + + var primaryUser = await _mareDbContext.Auth.Include(a => a.User).FirstOrDefaultAsync(f => f.PrimaryUserUID == uid); + + var toBanUid = primaryUser == null ? uid : primaryUser.UserUID; + + var lodestone = await _mareDbContext.LodeStoneAuth.Include(a => a.User).FirstOrDefaultAsync(c => c.User.UID == toBanUid); + + if (lodestone != null) + { + if (!_mareDbContext.BannedRegistrations.Any(c => c.DiscordIdOrLodestoneAuth == lodestone.HashedLodestoneId)) + { + _mareDbContext.BannedRegistrations.Add(new BannedRegistrations() + { + DiscordIdOrLodestoneAuth = lodestone.HashedLodestoneId, + }); + } + if (!_mareDbContext.BannedRegistrations.Any(c => c.DiscordIdOrLodestoneAuth == lodestone.DiscordId.ToString())) + { + _mareDbContext.BannedRegistrations.Add(new BannedRegistrations() + { + DiscordIdOrLodestoneAuth = lodestone.DiscordId.ToString(), + }); + } + + await _mareDbContext.SaveChangesAsync(); + } + } + + private async Task IsIdentBanned(string uid, string charaIdent) + { + var isBanned = await _mareDbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == charaIdent).ConfigureAwait(false); + if (isBanned) + { + var authToBan = _mareDbContext.Auth.SingleOrDefault(a => a.UserUID == uid); + if (authToBan != null) + { + authToBan.IsBanned = true; + await _mareDbContext.SaveChangesAsync().ConfigureAwait(false); + } + } + + return isBanned; + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.ClientStubs.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.ClientStubs.cs index cdfa2f2..ddf15a7 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.ClientStubs.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.ClientStubs.cs @@ -13,8 +13,6 @@ namespace MareSynchronosServer.Hubs public Task Client_GroupDelete(GroupDto groupDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); - public Task Client_GroupPairChangePermissions(GroupPairUserPermissionDto permissionDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); - public Task Client_GroupPairChangeUserInfo(GroupPairUserInfoDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); public Task Client_GroupPairJoined(GroupPairFullInfoDto groupPairInfoDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); @@ -46,5 +44,8 @@ namespace MareSynchronosServer.Hubs 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"); + public Task Client_UserUpdateDefaultPermissions(DefaultPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); + public Task Client_UpdateUserIndividualPairStatusDto(UserIndividualPairStatusDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); + public Task Client_GroupChangeUserPairPermissions(GroupPairUserPermissionDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); } } \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs index 2e1957e..89673d6 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs @@ -23,6 +23,10 @@ public partial class MareHub 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); + var defaultpermissions = await _dbContext.UserDefaultPreferredPermissions.SingleOrDefaultAsync(u => u.UserUID == user.UID).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 bannedEntries = await _dbContext.GroupBans.Where(u => u.BannedUserUID == user.UID).ToListAsync().ConfigureAwait(false); if (lodestone != null) { @@ -53,57 +57,33 @@ public partial class MareHub await UserLeaveGroup(new GroupDto(new GroupData(pair.GroupGID)), user.UID).ConfigureAwait(false); } + if (defaultpermissions != null) + { + _dbContext.UserDefaultPreferredPermissions.Remove(defaultpermissions); + } + _dbContext.GroupPairPreferredPermissions.RemoveRange(groupPermissions); + _dbContext.Permissions.RemoveRange(individualPermissions); + _dbContext.GroupBans.RemoveRange(bannedEntries); + await _dbContext.SaveChangesAsync().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> GetAllPairedClientsWithPauseState(string? uid = null) - { - uid ??= UserUID; - - var query = await (from userPair in _dbContext.ClientPairs - join otherUserPair in _dbContext.ClientPairs on userPair.OtherUserUID equals otherUserPair.UserUID - where otherUserPair.OtherUserUID == uid && userPair.UserUID == uid - select new - { - UID = Convert.ToString(userPair.OtherUserUID), - GID = "DIRECT", - PauseStateSelf = userPair.IsPaused, - PauseStateOther = otherUserPair.IsPaused, - }) - .Union( - (from userGroupPair in _dbContext.GroupPairs - join otherGroupPair in _dbContext.GroupPairs on userGroupPair.GroupGID equals otherGroupPair.GroupGID - where - userGroupPair.GroupUserUID == uid - && otherGroupPair.GroupUserUID != uid - select new - { - UID = Convert.ToString(otherGroupPair.GroupUserUID), - GID = Convert.ToString(otherGroupPair.GroupGID), - PauseStateSelf = userGroupPair.IsPaused, - PauseStateOther = otherGroupPair.IsPaused, - }) - ).AsNoTracking().ToListAsync().ConfigureAwait(false); - - return query.GroupBy(g => g.UID, g => (g.GID, g.PauseStateSelf, g.PauseStateOther), - (key, g) => new PausedEntry - { - UID = key, - PauseStates = g.Select(p => new PauseState() { GID = string.Equals(p.GID, "DIRECT", StringComparison.Ordinal) ? null : p.GID, IsSelfPaused = p.PauseStateSelf, IsOtherPaused = p.PauseStateOther }) - .ToList(), - }, StringComparer.Ordinal).ToList(); + _cacheService.ClearCache(user.UID); + _cacheService.MarkAsStale(null, user.UID); } private async Task> GetAllPairedUnpausedUsers(string? uid = null) { uid ??= UserUID; - var ret = await GetAllPairedClientsWithPauseState(uid).ConfigureAwait(false); - return ret.Where(k => !k.IsPaused).Select(k => k.UID).ToList(); + + return (await _cacheService.GetAllPairs(UserUID).ConfigureAwait(false)) + .Where(u => !u.Value.OwnPermissions.IsPaused && u.Value.OtherPermissions != null && !u.Value.OtherPermissions.IsPaused) + .Select(u => u.Key).ToList(); } private async Task> GetOnlineUsers(List uids) @@ -130,11 +110,9 @@ public partial class MareHub var pairIdent = await GetUserIdent(pair.GroupUserUID).ConfigureAwait(false); if (string.IsNullOrEmpty(pairIdent)) continue; - var pairs = await GetAllPairedClientsWithPauseState(pair.GroupUserUID).ConfigureAwait(false); - foreach (var groupUserPair in groupUsers.Where(g => !string.Equals(g.GroupUserUID, pair.GroupUserUID, StringComparison.Ordinal))) { - await UserGroupLeave(groupUserPair, pairs, pairIdent, pair.GroupUserUID).ConfigureAwait(false); + await UserGroupLeave(groupUserPair, pairIdent, pair.GroupUserUID).ConfigureAwait(false); } } } @@ -194,21 +172,18 @@ public partial class MareHub 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 allUserPairs, string userIdent, string? uid = null) + private async Task UserGroupLeave(GroupPair groupUserPair, string userIdent, string? uid = null) { uid ??= UserUID; - var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal)); - if (userPair != null) + var allUserPairs = await _cacheService.GetAllPairs(uid).ConfigureAwait(false); + if (!allUserPairs.TryGetValue(groupUserPair.GroupUserUID, out var info) || !info.IsSynced) { - 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); + 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); + } } } @@ -249,7 +224,7 @@ public partial class MareHub var user = await _dbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false); await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), - user.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false); + user.ToUserData(), group.ToEnum())).ConfigureAwait(false); } else { @@ -266,17 +241,20 @@ public partial class MareHub await _dbContext.SaveChangesAsync().ConfigureAwait(false); + _cacheService.MarkAsStale(userUid, null); + _logger.LogCallInfo(MareHubLogger.Args(dto, "Success")); await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupPairLeft(new GroupPairDto(dto.Group, groupPair.GroupUser.ToUserData())).ConfigureAwait(false); - var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); - var ident = await GetUserIdent(userUid).ConfigureAwait(false); foreach (var groupUserPair in groupPairsWithoutSelf) { - await UserGroupLeave(groupUserPair, allUserPairs, ident, userUid).ConfigureAwait(false); + await UserGroupLeave(groupUserPair, ident, userUid).ConfigureAwait(false); } } + + public record UserQueryPermissionEntry(UserQueryEntry OtherUser, UserPermissionSet OwnPermissions, UserPermissionSet? OtherPermissions); + public record UserQueryEntry(string UID, bool IsPaired, string GID, string Alias); } \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs index 436ebcf..b7b8ce0 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs @@ -53,9 +53,9 @@ public partial class MareHub if (!hasRights) return; group.InvitesEnabled = !dto.Permissions.HasFlag(GroupPermissions.DisableInvites); - group.DisableSounds = dto.Permissions.HasFlag(GroupPermissions.DisableSounds); - group.DisableAnimations = dto.Permissions.HasFlag(GroupPermissions.DisableAnimations); - group.DisableVFX = dto.Permissions.HasFlag(GroupPermissions.DisableVFX); + group.PreferDisableSounds = dto.Permissions.HasFlag(GroupPermissions.PreferDisableSounds); + group.PreferDisableAnimations = dto.Permissions.HasFlag(GroupPermissions.PreferDisableAnimations); + group.PreferDisableVFX = dto.Permissions.HasFlag(GroupPermissions.PreferDisableVFX); await _dbContext.SaveChangesAsync().ConfigureAwait(false); @@ -71,47 +71,81 @@ public partial class MareHub var (inGroup, groupPair) = await TryValidateUserInGroup(dto.Group.GID).ConfigureAwait(false); if (!inGroup) return; - var wasPaused = groupPair.IsPaused; - groupPair.DisableSounds = dto.GroupPairPermissions.IsDisableSounds(); - groupPair.DisableAnimations = dto.GroupPairPermissions.IsDisableAnimations(); - groupPair.IsPaused = dto.GroupPairPermissions.IsPaused(); - groupPair.DisableVFX = dto.GroupPairPermissions.IsDisableVFX(); + var groupPreferredPermissions = await _dbContext.GroupPairPreferredPermissions + .SingleAsync(u => u.UserUID == UserUID && u.GroupGID == dto.Group.GID).ConfigureAwait(false); + var wasPaused = groupPreferredPermissions.IsPaused; + groupPreferredPermissions.DisableSounds = dto.GroupPairPermissions.IsDisableSounds(); + groupPreferredPermissions.DisableAnimations = dto.GroupPairPermissions.IsDisableAnimations(); + groupPreferredPermissions.IsPaused = dto.GroupPairPermissions.IsPaused(); + groupPreferredPermissions.DisableVFX = dto.GroupPairPermissions.IsDisableVFX(); + + // set the permissions for every group pair that is not sticky + var allPairs = (await _cacheService.GetAllPairs(UserUID).ConfigureAwait(false)) + .Where(u => !u.Value.OwnPermissions.Sticky) + .ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); + + var affectedGroupPairs = allPairs.Where(u => u.Value.GIDs.Contains(dto.GID, StringComparer.Ordinal)).ToList(); + var groupUserUids = affectedGroupPairs.Select(g => g.Key).ToList(); + var affectedPerms = await _dbContext.Permissions.Where(u => u.UserUID == UserUID + && groupUserUids.Any(c => c == u.OtherUserUID)) + .ToListAsync().ConfigureAwait(false); + + foreach (var perm in affectedPerms) + { + perm.DisableSounds = groupPreferredPermissions.DisableSounds; + perm.DisableAnimations = groupPreferredPermissions.DisableAnimations; + perm.IsPaused = groupPreferredPermissions.IsPaused; + perm.DisableVFX = groupPreferredPermissions.DisableVFX; + } await _dbContext.SaveChangesAsync().ConfigureAwait(false); - var groupPairs = _dbContext.GroupPairs.Include(p => p.GroupUser).Where(p => p.GroupGID == dto.Group.GID).ToList(); - await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).Client_GroupPairChangePermissions(dto).ConfigureAwait(false); + foreach (var item in affectedGroupPairs) + { + _cacheService.MarkAsStale(UserUID, item.Key); + } + + // send messages + UserPermissions permissions = UserPermissions.NoneSet; + permissions.SetPaused(groupPreferredPermissions.IsPaused); + permissions.SetDisableAnimations(groupPreferredPermissions.DisableAnimations); + permissions.SetDisableSounds(groupPreferredPermissions.DisableSounds); + permissions.SetDisableVFX(groupPreferredPermissions.DisableVFX); + + // send apporpriate permission set to each user + await Clients.Users(affectedGroupPairs + .Select(k => k.Key)) + .Client_UserUpdateOtherPairPermissions(new(new(UserUID), permissions)).ConfigureAwait(false); + + await Clients.User(UserUID).Client_GroupChangeUserPairPermissions(dto).ConfigureAwait(false); + foreach (var item in affectedGroupPairs.Select(k => k.Key)) + { + await Clients.User(UserUID).Client_UserUpdateSelfPairPermissions(new(new(item), permissions)).ConfigureAwait(false); + } - var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); var self = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); - if (wasPaused == groupPair.IsPaused) return; + if (wasPaused == groupPreferredPermissions.IsPaused) return; - foreach (var groupUserPair in groupPairs.Where(u => !string.Equals(u.GroupUserUID, UserUID, StringComparison.Ordinal)).ToList()) + foreach (var groupUserPair in affectedGroupPairs) { - var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal)); - if (userPair != null) - { - if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) continue; - if (userPair.IsPausedExcludingGroup(dto.Group.GID) is PauseInfo.Unpaused) continue; - if (userPair.IsOtherPausedForSpecificGroup(dto.Group.GID) is PauseInfo.Paused) continue; - } - - var groupUserIdent = await GetUserIdent(groupUserPair.GroupUserUID).ConfigureAwait(false); + var groupUserIdent = await GetUserIdent(groupUserPair.Key).ConfigureAwait(false); if (!string.IsNullOrEmpty(groupUserIdent)) { - if (!groupPair.IsPaused) + // if we changed to paused and other was not paused before, we send offline + if (groupPreferredPermissions.IsPaused && !groupUserPair.Value.OtherPermissions.IsPaused) { - await Clients.User(UserUID).Client_UserSendOnline(new(groupUserPair.ToUserData(), groupUserIdent)).ConfigureAwait(false); - await Clients.User(groupUserPair.GroupUserUID) - .Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false); - } - else - { - await Clients.User(UserUID).Client_UserSendOffline(new(groupUserPair.ToUserData())).ConfigureAwait(false); - await Clients.User(groupUserPair.GroupUserUID) + await Clients.User(UserUID).Client_UserSendOffline(new(new(groupUserPair.Key, groupUserPair.Value.Alias))).ConfigureAwait(false); + await Clients.User(groupUserPair.Key) .Client_UserSendOffline(new(self.ToUserData())).ConfigureAwait(false); } + // if we changed to unpaused and other was not paused either we send online + else if (!groupPreferredPermissions.IsPaused && !groupUserPair.Value.OtherPermissions.IsPaused) + { + await Clients.User(UserUID).Client_UserSendOnline(new(new(groupUserPair.Key, groupUserPair.Value.Alias), groupUserIdent)).ConfigureAwait(false); + await Clients.User(groupUserPair.Key) + .Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false); + } } } } @@ -142,7 +176,7 @@ public partial class MareHub var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == dto.Group.GID).Select(p => p.GroupUserUID).AsNoTracking().ToListAsync().ConfigureAwait(false); - await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), newOwnerPair.GroupUser.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false); + await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), newOwnerPair.GroupUser.ToUserData(), group.ToEnum())).ConfigureAwait(false); } [Authorize(Policy = "Identified")] @@ -179,25 +213,28 @@ public partial class MareHub _dbContext.GroupPairs.RemoveRange(notPinned); await _dbContext.SaveChangesAsync().ConfigureAwait(false); + foreach (var user in notPinned) + { + _cacheService.MarkAsStale(user.GroupUserUID, null); + } foreach (var pair in notPinned) { - await Clients.Users(groupPairs.Where(p => p.IsPinned).Select(g => g.GroupUserUID)).Client_GroupPairLeft(new GroupPairDto(dto.Group, pair.GroupUser.ToUserData())).ConfigureAwait(false); + await Clients.Users(groupPairs.Where(p => p.IsPinned || p.IsModerator).Select(g => g.GroupUserUID)) + .Client_GroupPairLeft(new GroupPairDto(dto.Group, pair.GroupUser.ToUserData())).ConfigureAwait(false); var pairIdent = await GetUserIdent(pair.GroupUserUID).ConfigureAwait(false); if (string.IsNullOrEmpty(pairIdent)) continue; - var allUserPairs = await GetAllPairedClientsWithPauseState(pair.GroupUserUID).ConfigureAwait(false); - foreach (var groupUserPair in groupPairs.Where(p => !string.Equals(p.GroupUserUID, pair.GroupUserUID, StringComparison.Ordinal))) { - await UserGroupLeave(groupUserPair, allUserPairs, pairIdent).ConfigureAwait(false); + await UserGroupLeave(groupUserPair, pairIdent, pair.GroupUserUID).ConfigureAwait(false); } } } [Authorize(Policy = "Identified")] - public async Task GroupCreate() + public async Task GroupCreate() { _logger.LogCallInfo(); var existingGroupsByUser = await _dbContext.Groups.CountAsync(u => u.OwnerUID == UserUID).ConfigureAwait(false); @@ -215,37 +252,52 @@ public partial class MareHub gid = "MSS-" + gid; var passwd = StringUtils.GenerateRandomString(16); - var sha = SHA256.Create(); + using var sha = SHA256.Create(); var hashedPw = StringUtils.Sha256String(passwd); + UserDefaultPreferredPermission defaultPermissions = await _dbContext.UserDefaultPreferredPermissions.SingleAsync(u => u.UserUID == UserUID).ConfigureAwait(false); + Group newGroup = new() { GID = gid, HashedPassword = hashedPw, InvitesEnabled = true, OwnerUID = UserUID, + PreferDisableAnimations = defaultPermissions.DisableGroupAnimations, + PreferDisableSounds = defaultPermissions.DisableGroupSounds, + PreferDisableVFX = defaultPermissions.DisableGroupVFX }; GroupPair initialPair = new() { GroupGID = newGroup.GID, GroupUserUID = UserUID, - IsPaused = false, IsPinned = true, }; + GroupPairPreferredPermission initialPrefPermissions = new() + { + UserUID = UserUID, + GroupGID = newGroup.GID, + DisableSounds = defaultPermissions.DisableGroupSounds, + DisableAnimations = defaultPermissions.DisableGroupAnimations, + DisableVFX = defaultPermissions.DisableGroupAnimations + }; + await _dbContext.Groups.AddAsync(newGroup).ConfigureAwait(false); await _dbContext.GroupPairs.AddAsync(initialPair).ConfigureAwait(false); + await _dbContext.GroupPairPreferredPermissions.AddAsync(initialPrefPermissions).ConfigureAwait(false); await _dbContext.SaveChangesAsync().ConfigureAwait(false); var self = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); - await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None)) + await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), + newGroup.ToEnum(), initialPrefPermissions.ToEnum(), initialPair.ToEnum(), new(StringComparer.Ordinal))) .ConfigureAwait(false); _logger.LogCallInfo(MareHubLogger.Args(gid)); - return new GroupPasswordDto(newGroup.ToGroupData(), passwd); + return new GroupJoinDto(newGroup.ToGroupData(), passwd, initialPrefPermissions.ToEnum()); } [Authorize(Policy = "Identified")] @@ -326,11 +378,39 @@ public partial class MareHub } [Authorize(Policy = "Identified")] - public async Task GroupJoin(GroupPasswordDto dto) + public async Task GroupJoin(GroupPasswordDto dto) { var aliasOrGid = dto.Group.GID.Trim(); - _logger.LogCallInfo(MareHubLogger.Args(dto.Group)); + _logger.LogCallInfo(MareHubLogger.Args(dto)); + + var group = await _dbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == aliasOrGid || g.Alias == aliasOrGid).ConfigureAwait(false); + var groupGid = group?.GID ?? string.Empty; + var existingPair = await _dbContext.GroupPairs.AsNoTracking().SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.GroupUserUID == UserUID).ConfigureAwait(false); + var hashedPw = StringUtils.Sha256String(dto.Password); + var existingUserCount = await _dbContext.GroupPairs.AsNoTracking().CountAsync(g => g.GroupGID == groupGid).ConfigureAwait(false); + var joinedGroups = await _dbContext.GroupPairs.CountAsync(g => g.GroupUserUID == UserUID).ConfigureAwait(false); + var isBanned = await _dbContext.GroupBans.AnyAsync(g => g.GroupGID == groupGid && g.BannedUserUID == UserUID).ConfigureAwait(false); + var oneTimeInvite = await _dbContext.GroupTempInvites.SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.Invite == hashedPw).ConfigureAwait(false); + + if (group == null + || (!string.Equals(group.HashedPassword, hashedPw, StringComparison.Ordinal) && oneTimeInvite == null) + || existingPair != null + || existingUserCount >= _maxGroupUserCount + || !group.InvitesEnabled + || joinedGroups >= _maxJoinedGroupsByUser + || isBanned) + return new GroupJoinInfoDto(null, null, GroupPermissions.NoneSet, false); + + return new GroupJoinInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.ToEnum(), true); + } + + [Authorize(Policy = "Identified")] + public async Task GroupJoinFinalize(GroupJoinDto dto) + { + var aliasOrGid = dto.Group.GID.Trim(); + + _logger.LogCallInfo(MareHubLogger.Args(dto)); var group = await _dbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == aliasOrGid || g.Alias == aliasOrGid).ConfigureAwait(false); var groupGid = group?.GID ?? string.Empty; @@ -350,6 +430,10 @@ public partial class MareHub || isBanned) return false; + // get all pairs before we join + var allUserPairs = (await _cacheService.GetAllPairs(UserUID).ConfigureAwait(false)) + .ToDictionary(u => u.Key, u => u.Value, StringComparer.Ordinal); + if (oneTimeInvite != null) { _logger.LogCallInfo(MareHubLogger.Args(aliasOrGid, "TempInvite", oneTimeInvite.Invite)); @@ -360,47 +444,151 @@ public partial class MareHub { GroupGID = group.GID, GroupUserUID = UserUID, - DisableAnimations = false, - DisableSounds = false, - DisableVFX = false }; + var preferredPermissions = await _dbContext.GroupPairPreferredPermissions.SingleOrDefaultAsync(u => u.UserUID == UserUID && u.GroupGID == group.GID).ConfigureAwait(false); + if (preferredPermissions == null) + { + GroupPairPreferredPermission newPerms = new() + { + GroupGID = group.GID, + UserUID = UserUID, + DisableSounds = dto.GroupUserPreferredPermissions.IsDisableSounds(), + DisableVFX = dto.GroupUserPreferredPermissions.IsDisableVFX(), + DisableAnimations = dto.GroupUserPreferredPermissions.IsDisableAnimations(), + IsPaused = false + }; + + _dbContext.Add(newPerms); + preferredPermissions = newPerms; + } + else + { + preferredPermissions.DisableSounds = dto.GroupUserPreferredPermissions.IsDisableSounds(); + preferredPermissions.DisableVFX = dto.GroupUserPreferredPermissions.IsDisableVFX(); + preferredPermissions.DisableAnimations = dto.GroupUserPreferredPermissions.IsDisableAnimations(); + preferredPermissions.IsPaused = false; + _dbContext.Update(preferredPermissions); + } + await _dbContext.GroupPairs.AddAsync(newPair).ConfigureAwait(false); await _dbContext.SaveChangesAsync().ConfigureAwait(false); _logger.LogCallInfo(MareHubLogger.Args(aliasOrGid, "Success")); - await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.GetGroupPermissions(), newPair.GetGroupPairPermissions(), newPair.GetGroupPairUserInfo())).ConfigureAwait(false); + var groupInfos = await _dbContext.GroupPairs.Where(u => u.GroupGID == group.GID && (u.IsPinned || u.IsModerator)).ToListAsync().ConfigureAwait(false); + await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), group.Owner.ToUserData(), + group.ToEnum(), preferredPermissions.ToEnum(), newPair.ToEnum(), + groupInfos.ToDictionary(u => u.GroupUserUID, u => u.ToEnum(), StringComparer.Ordinal))).ConfigureAwait(false); var self = _dbContext.Users.Single(u => u.UID == UserUID); var groupPairs = await _dbContext.GroupPairs.Include(p => p.GroupUser).Where(p => p.GroupGID == group.GID && p.GroupUserUID != UserUID).ToListAsync().ConfigureAwait(false); - await Clients.Users(groupPairs.Select(p => p.GroupUserUID)) - .Client_GroupPairJoined(new GroupPairFullInfoDto(group.ToGroupData(), self.ToUserData(), newPair.GetGroupPairUserInfo(), newPair.GetGroupPairPermissions())).ConfigureAwait(false); + _cacheService.MarkAsStale(UserUID, null); foreach (var pair in groupPairs) { - await Clients.User(UserUID).Client_GroupPairJoined(new GroupPairFullInfoDto(group.ToGroupData(), pair.ToUserData(), pair.GetGroupPairUserInfo(), pair.GetGroupPairPermissions())).ConfigureAwait(false); + _cacheService.MarkAsStale(UserUID, pair.GroupUserUID); } - var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); + var userPairsAfterJoin = await _cacheService.GetAllPairs(UserUID).ConfigureAwait(false); - foreach (var groupUserPair in groupPairs) + foreach (var pair in groupPairs) { - var userPair = allUserPairs.Single(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal)); - if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) continue; - if (userPair.IsPausedExcludingGroup(group.GID) is PauseInfo.Unpaused) continue; - if (userPair.IsPausedPerGroup is PauseInfo.Paused) continue; - - var groupUserIdent = await GetUserIdent(groupUserPair.GroupUserUID).ConfigureAwait(false); - if (!string.IsNullOrEmpty(groupUserIdent)) + var perms = userPairsAfterJoin.TryGetValue(pair.GroupUserUID, out var userinfo); + // check if we have had prior permissions to that pair, if not add them + var ownPermissionsToOther = userinfo?.OwnPermissions ?? null; + if (ownPermissionsToOther == null) { - await Clients.User(UserUID).Client_UserSendOnline(new(groupUserPair.ToUserData(), groupUserIdent)).ConfigureAwait(false); - await Clients.User(groupUserPair.GroupUserUID) - .Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false); + var existingPermissionsOnDb = await _dbContext.Permissions.SingleOrDefaultAsync(p => p.UserUID == UserUID && p.OtherUserUID == pair.GroupUserUID).ConfigureAwait(false); + + if (existingPermissionsOnDb == null) + { + ownPermissionsToOther = new() + { + UserUID = UserUID, + OtherUserUID = pair.GroupUserUID, + DisableAnimations = preferredPermissions.DisableAnimations, + DisableSounds = preferredPermissions.DisableSounds, + DisableVFX = preferredPermissions.DisableVFX, + IsPaused = false, + Sticky = false + }; + + await _dbContext.Permissions.AddAsync(ownPermissionsToOther).ConfigureAwait(false); + } + else + { + existingPermissionsOnDb.DisableAnimations = preferredPermissions.DisableAnimations; + existingPermissionsOnDb.DisableSounds = preferredPermissions.DisableSounds; + existingPermissionsOnDb.DisableVFX = preferredPermissions.DisableVFX; + existingPermissionsOnDb.IsPaused = false; + existingPermissionsOnDb.Sticky = false; + + _dbContext.Update(existingPermissionsOnDb); + + ownPermissionsToOther = existingPermissionsOnDb; + } + + } + else if (!ownPermissionsToOther.Sticky) + { + ownPermissionsToOther = await _dbContext.Permissions.SingleAsync(u => u.UserUID == UserUID && u.OtherUserUID == pair.GroupUserUID).ConfigureAwait(false); + + // update the existing permission only if it was not set to sticky + ownPermissionsToOther.DisableAnimations = preferredPermissions.DisableAnimations; + ownPermissionsToOther.DisableVFX = preferredPermissions.DisableVFX; + ownPermissionsToOther.DisableSounds = preferredPermissions.DisableSounds; + ownPermissionsToOther.IsPaused = false; + + _dbContext.Update(ownPermissionsToOther); + } + + // get others permissionset to self and eventually update it + var otherPermissionToSelf = userinfo?.OtherPermissions ?? null; + if (otherPermissionToSelf == null) + { + var otherPreferred = await _dbContext.GroupPairPreferredPermissions.SingleAsync(u => u.GroupGID == group.GID && u.UserUID == pair.GroupUserUID).ConfigureAwait(false); + otherPermissionToSelf = new() + { + UserUID = pair.GroupUserUID, + OtherUserUID = UserUID, + DisableAnimations = otherPreferred.DisableAnimations, + DisableSounds = otherPreferred.DisableSounds, + DisableVFX = otherPreferred.DisableVFX, + IsPaused = otherPreferred.IsPaused, + Sticky = false + }; + + await _dbContext.AddAsync(otherPermissionToSelf).ConfigureAwait(false); + } + + await Clients.User(UserUID).Client_GroupPairJoined(new GroupPairFullInfoDto(group.ToGroupData(), + pair.ToUserData(), ownPermissionsToOther.ToUserPermissions(setSticky: ownPermissionsToOther.Sticky), + otherPermissionToSelf.ToUserPermissions(setSticky: false))).ConfigureAwait(false); + await Clients.User(pair.GroupUserUID).Client_GroupPairJoined(new GroupPairFullInfoDto(group.ToGroupData(), + self.ToUserData(), otherPermissionToSelf.ToUserPermissions(setSticky: false), + ownPermissionsToOther.ToUserPermissions(setSticky: false))).ConfigureAwait(false); + + // if not paired prior and neither has the permissions set to paused, send online + if ((!allUserPairs.ContainsKey(pair.GroupUserUID) || (allUserPairs.ContainsKey(pair.GroupUserUID) && !allUserPairs[pair.GroupUserUID].IsSynced)) + && !otherPermissionToSelf.IsPaused && !ownPermissionsToOther.IsPaused) + { + var groupUserIdent = await GetUserIdent(pair.GroupUserUID).ConfigureAwait(false); + + await Clients.User(UserUID).Client_UserSendOnline(new(pair.ToUserData(), groupUserIdent)).ConfigureAwait(false); + await Clients.User(pair.GroupUserUID).Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false); } } + _cacheService.MarkAsStale(UserUID, null); + foreach (var pair in groupPairs) + { + _cacheService.MarkAsStale(UserUID, pair.GroupUserUID); + } + + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + return true; } @@ -426,6 +614,11 @@ public partial class MareHub _dbContext.GroupPairs.Remove(groupPair); await _dbContext.SaveChangesAsync().ConfigureAwait(false); + _cacheService.MarkAsStale(dto.User.UID, null); + foreach (var user in await _dbContext.GroupPairs.Where(u => u.GroupGID == dto.GID).ToListAsync().ConfigureAwait(false)) + { + _cacheService.MarkAsStale(user.GroupUserUID, null); + } var groupPairs = _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID).AsNoTracking().ToList(); await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).Client_GroupPairLeft(dto).ConfigureAwait(false); @@ -435,11 +628,9 @@ public partial class MareHub await Clients.User(dto.User.UID).Client_GroupDelete(new GroupDto(dto.Group)).ConfigureAwait(false); - var allUserPairs = await GetAllPairedClientsWithPauseState(dto.User.UID).ConfigureAwait(false); - foreach (var groupUserPair in groupPairs) { - await UserGroupLeave(groupUserPair, allUserPairs, userIdent, dto.User.UID).ConfigureAwait(false); + await UserGroupLeave(groupUserPair, userIdent, dto.User.UID).ConfigureAwait(false); } } @@ -454,7 +645,7 @@ public partial class MareHub var (userIsOwner, _) = await TryValidateOwner(dto.Group.GID).ConfigureAwait(false); var (userIsModerator, _) = await TryValidateGroupModeratorOrOwner(dto.Group.GID).ConfigureAwait(false); - if (dto.GroupUserInfo.HasFlag(GroupUserInfo.IsPinned) && userIsModerator && !userPair.IsPinned) + if (dto.GroupUserInfo.HasFlag(GroupPairUserInfo.IsPinned) && userIsModerator && !userPair.IsPinned) { userPair.IsPinned = true; } @@ -463,7 +654,7 @@ public partial class MareHub userPair.IsPinned = false; } - if (dto.GroupUserInfo.HasFlag(GroupUserInfo.IsModerator) && userIsOwner && !userPair.IsModerator) + if (dto.GroupUserInfo.HasFlag(GroupPairUserInfo.IsModerator) && userIsOwner && !userPair.IsModerator) { userPair.IsModerator = true; } @@ -475,7 +666,7 @@ public partial class MareHub await _dbContext.SaveChangesAsync().ConfigureAwait(false); var groupPairs = await _dbContext.GroupPairs.AsNoTracking().Where(p => p.GroupGID == dto.Group.GID).Select(p => p.GroupUserUID).ToListAsync().ConfigureAwait(false); - await Clients.Users(groupPairs).Client_GroupPairChangeUserInfo(new GroupPairUserInfoDto(dto.Group, dto.User, userPair.GetGroupPairUserInfo())).ConfigureAwait(false); + await Clients.Users(groupPairs).Client_GroupPairChangeUserInfo(new GroupPairUserInfoDto(dto.Group, dto.User, userPair.ToEnum())).ConfigureAwait(false); } [Authorize(Policy = "Identified")] @@ -484,22 +675,16 @@ public partial class MareHub _logger.LogCallInfo(); var groups = await _dbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false); + var preferredPermissions = (await _dbContext.GroupPairPreferredPermissions.Where(u => u.UserUID == UserUID).ToListAsync().ConfigureAwait(false)) + .Where(u => groups.Exists(k => string.Equals(k.GroupGID, u.GroupGID, StringComparison.Ordinal))) + .ToDictionary(u => groups.First(f => string.Equals(f.GroupGID, u.GroupGID, StringComparison.Ordinal)), u => u); + var groupInfos = await _dbContext.GroupPairs.Where(u => groups.Select(g => g.GroupGID).Contains(u.GroupGID) && (u.IsPinned || u.IsModerator)) + .ToListAsync().ConfigureAwait(false); - return groups.Select(g => new GroupFullInfoDto(g.Group.ToGroupData(), g.Group.Owner.ToUserData(), - g.Group.GetGroupPermissions(), g.GetGroupPairPermissions(), g.GetGroupPairUserInfo())).ToList(); - } - - [Authorize(Policy = "Identified")] - public async Task> GroupsGetUsersInGroup(GroupDto dto) - { - _logger.LogCallInfo(MareHubLogger.Args(dto)); - - var (inGroup, _) = await TryValidateUserInGroup(dto.Group.GID).ConfigureAwait(false); - if (!inGroup) return new List(); - - var group = await _dbContext.Groups.SingleAsync(g => g.GID == dto.Group.GID).ConfigureAwait(false); - var allPairs = await _dbContext.GroupPairs.Include(g => g.GroupUser).Where(g => g.GroupGID == dto.Group.GID && g.GroupUserUID != UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false); - return allPairs.Select(p => new GroupPairFullInfoDto(group.ToGroupData(), p.GroupUser.ToUserData(), p.GetGroupPairUserInfo(), p.GetGroupPairPermissions())).ToList(); + return preferredPermissions.Select(g => new GroupFullInfoDto(g.Key.Group.ToGroupData(), g.Key.Group.Owner.ToUserData(), + g.Key.Group.ToEnum(), g.Value.ToEnum(), g.Key.ToEnum(), + groupInfos.Where(i => string.Equals(i.GroupGID, g.Key.GroupGID, StringComparison.Ordinal)) + .ToDictionary(i => i.GroupUserUID, i => i.ToEnum(), StringComparer.Ordinal))).ToList(); } [Authorize(Policy = "Identified")] diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs index 654d22a..78f8d61 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Extensions; +using MareSynchronos.API.Dto; using MareSynchronos.API.Dto.User; using MareSynchronosServer.Utils; using MareSynchronosShared.Metrics; @@ -59,31 +60,80 @@ public partial class MareHub ClientPair wl = new ClientPair() { - IsPaused = false, OtherUser = otherUser, User = user, }; await _dbContext.ClientPairs.AddAsync(wl).ConfigureAwait(false); await _dbContext.SaveChangesAsync().ConfigureAwait(false); + _cacheService.MarkAsStale(UserUID, otherUser.UID); + + var existingData = await _cacheService.GetPairData(UserUID, otherUser.UID).ConfigureAwait(false); + + var permissions = existingData.OwnPermissions; + if (permissions == null || !permissions.Sticky) + { + var ownDefaultPermissions = await _dbContext.UserDefaultPreferredPermissions.AsNoTracking().SingleOrDefaultAsync(f => f.UserUID == UserUID).ConfigureAwait(false); + + permissions = new UserPermissionSet() + { + User = user, + OtherUser = otherUser, + DisableAnimations = ownDefaultPermissions.DisableIndividualAnimations, + DisableSounds = ownDefaultPermissions.DisableIndividualSounds, + DisableVFX = ownDefaultPermissions.DisableIndividualVFX, + IsPaused = false, + Sticky = true + }; + + var existingDbPerms = await _dbContext.Permissions.SingleOrDefaultAsync(u => u.UserUID == UserUID && u.OtherUserUID == otherUser.UID).ConfigureAwait(false); + if (existingDbPerms == null) + { + await _dbContext.Permissions.AddAsync(permissions).ConfigureAwait(false); + } + else + { + existingDbPerms.DisableAnimations = permissions.DisableAnimations; + existingDbPerms.DisableSounds = permissions.DisableSounds; + existingDbPerms.DisableVFX = permissions.DisableVFX; + existingDbPerms.IsPaused = false; + existingDbPerms.Sticky = true; + + _dbContext.Permissions.Update(existingDbPerms); + } + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + _cacheService.MarkAsStale(UserUID, otherUser.UID); + // get the opposite entry of the client pair var otherEntry = OppositeEntry(otherUser.UID); var otherIdent = await GetUserIdent(otherUser.UID).ConfigureAwait(false); - var ownPerm = UserPermissions.Paired; - var otherPerm = UserPermissions.NoneSet; - otherPerm.SetPaired(otherEntry != null); - otherPerm.SetPaused(otherEntry?.IsPaused ?? false); - var userPairResponse = new UserPairDto(otherUser.ToUserData(), ownPerm, otherPerm); + var otherPermissions = existingData?.OtherPermissions ?? null; + + var ownPerm = permissions.ToUserPermissions(setSticky: true); + var otherPerm = otherPermissions.ToUserPermissions(); + + var userPairResponse = new UserPairDto(otherUser.ToUserData(), + otherEntry == null ? IndividualPairStatus.OneSided : IndividualPairStatus.Bidirectional, + ownPerm, otherPerm); + await Clients.User(user.UID).Client_UserAddClientPair(userPairResponse).ConfigureAwait(false); // check if other user is online if (otherIdent == null || otherEntry == null) return; // send push with update to other user if other user is online - await Clients.User(otherUser.UID).Client_UserAddClientPair(new UserPairDto(user.ToUserData(), otherPerm, ownPerm)).ConfigureAwait(false); + await Clients.User(otherUser.UID) + .Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(user.ToUserData(), + permissions.ToUserPermissions())).ConfigureAwait(false); - if (!otherPerm.IsPaused()) + await Clients.User(otherUser.UID) + .Client_UpdateUserIndividualPairStatusDto(new(user.ToUserData(), IndividualPairStatus.Bidirectional)) + .ConfigureAwait(false); + + if (!ownPerm.IsPaused() && !otherPerm.IsPaused()) { await Clients.User(UserUID).Client_UserSendOnline(new(otherUser.ToUserData(), otherIdent)).ConfigureAwait(false); await Clients.User(otherUser.UID).Client_UserSendOnline(new(user.ToUserData(), UserCharaIdent)).ConfigureAwait(false); @@ -113,60 +163,24 @@ public partial class MareHub var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); + await SendOnlineToAllPairedUsers().ConfigureAwait(false); + return pairs.Select(p => new OnlineUserIdentDto(new UserData(p.Key), p.Value)).ToList(); } [Authorize(Policy = "Identified")] - public async Task> UserGetPairedClients() + public async Task> 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, - DisableOwnVFX = userToOther.DisableVFX, - DisableOtherAnimations = otherEntry == null ? false : otherEntry.DisableAnimations, - DisableOtherSounds = otherEntry == null ? false : otherEntry.DisableSounds, - DisableOtherVFX = otherEntry == null ? false : otherEntry.DisableVFX - }; - - var results = await query.AsNoTracking().ToListAsync().ConfigureAwait(false); - - return results.Select(c => + var pairs = await _cacheService.GetAllPairs(UserUID).ConfigureAwait(false); + return pairs.Select(p => { - var ownPerm = UserPermissions.Paired; - ownPerm.SetPaused(c.IsPaused); - ownPerm.SetDisableAnimations(c.DisableOwnAnimations); - ownPerm.SetDisableSounds(c.DisableOwnSounds); - ownPerm.SetDisableVFX(c.DisableOwnVFX); - var otherPerm = UserPermissions.NoneSet; - otherPerm.SetPaired(c.IsSynced); - otherPerm.SetPaused(c.OtherIsPaused); - otherPerm.SetDisableAnimations(c.DisableOtherAnimations); - otherPerm.SetDisableSounds(c.DisableOtherSounds); - otherPerm.SetDisableVFX(c.DisableOtherVFX); - return new UserPairDto(new(c.OtherUserUID, c.Alias), ownPerm, otherPerm); + return new UserFullPairDto(new UserData(p.Key, p.Value.Alias), + p.Value.ToIndividualPairStatus(), + p.Value.GIDs.Where(g => !string.Equals(g, Constants.IndividualKeyword, StringComparison.OrdinalIgnoreCase)).ToList(), + p.Value.OwnPermissions.ToUserPermissions(setSticky: true), + p.Value.OtherPermissions.ToUserPermissions()); }).ToList(); } @@ -251,6 +265,9 @@ public partial class MareHub + string.Join(Environment.NewLine, invalidFileSwapPaths.Select(p => "Invalid FileSwap Path: " + p))); } + var allPairs = await _cacheService.GetAllPairs(UserUID).ConfigureAwait(false); + allPairs.Where(p => !p.Value.OwnPermissions.IsPaused && p.Value.OtherPermissions != null && !p.Value.OtherPermissions.IsPaused).ToList(); + var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); var idents = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); @@ -276,53 +293,46 @@ public partial class MareHub await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false); if (callerPair == null) return; - bool callerHadPaused = callerPair.IsPaused; + var pairData = await _cacheService.GetPairData(UserUID, dto.User.UID).ConfigureAwait(false); // delete from database, send update info to users pair list _dbContext.ClientPairs.Remove(callerPair); await _dbContext.SaveChangesAsync().ConfigureAwait(false); + _cacheService.MarkAsStale(UserUID, dto.User.UID); _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; + if (!pairData.IndividuallyPaired) 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; + bool callerHadPaused = pairData.OwnPermissions?.IsPaused ?? false; - 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; + // send updated individual pair status + await Clients.User(dto.User.UID) + .Client_UpdateUserIndividualPairStatusDto(new(new(UserUID), IndividualPairStatus.OneSided)) + .ConfigureAwait(false); - // 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; + UserPermissionSet? otherPermissions = pairData.OtherPermissions; + bool otherHadPaused = otherPermissions?.IsPaused ?? true; + + // if the either had paused, do nothing + if (callerHadPaused && otherHadPaused) return; + + var currentPairData = await _cacheService.GetPairData(UserUID, dto.User.UID).ConfigureAwait(false); // 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) + if (!currentPairData?.IsSynced ?? true) { 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")] @@ -373,44 +383,57 @@ public partial class MareHub _logger.LogCallInfo(MareHubLogger.Args(dto)); if (string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) return; - ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false); - if (pair == null) return; + UserPermissionSet prevPermissions = await _dbContext.Permissions.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false); + if (prevPermissions == null) return; // you always should have permissions to another user - var pauseChange = pair.IsPaused != dto.Permissions.IsPaused(); + var oldPairData = await _cacheService.GetPairData(UserUID, dto.User.UID).ConfigureAwait(false); + bool setSticky = false; + if (!oldPairData.GIDs.Contains(Constants.IndividualKeyword, StringComparer.Ordinal)) + { + if (!oldPairData.OwnPermissions.Sticky && !dto.Permissions.IsSticky()) + { + var defaultPermissions = await _dbContext.UserDefaultPreferredPermissions.SingleAsync(u => u.UserUID == UserUID).ConfigureAwait(false); + setSticky = defaultPermissions.IndividualIsSticky; + } + } - pair.IsPaused = dto.Permissions.IsPaused(); - pair.DisableAnimations = dto.Permissions.IsDisableAnimations(); - pair.DisableSounds = dto.Permissions.IsDisableSounds(); - pair.DisableVFX = dto.Permissions.IsDisableVFX(); - _dbContext.Update(pair); + var pauseChange = prevPermissions.IsPaused != dto.Permissions.IsPaused(); + + prevPermissions.IsPaused = dto.Permissions.IsPaused(); + prevPermissions.DisableAnimations = dto.Permissions.IsDisableAnimations(); + prevPermissions.DisableSounds = dto.Permissions.IsDisableSounds(); + prevPermissions.DisableVFX = dto.Permissions.IsDisableVFX(); + prevPermissions.Sticky = dto.Permissions.IsSticky() || setSticky; + _dbContext.Update(prevPermissions); await _dbContext.SaveChangesAsync().ConfigureAwait(false); + _cacheService.MarkAsStale(UserUID, dto.User.UID); _logger.LogCallInfo(MareHubLogger.Args(dto, "Success")); - var otherEntry = OppositeEntry(dto.User.UID); + var permCopy = dto.Permissions; + permCopy.SetSticky(dto.Permissions.IsSticky() || setSticky); - await Clients.User(UserUID).Client_UserUpdateSelfPairPermissions(dto).ConfigureAwait(false); + await Clients.User(UserUID).Client_UserUpdateSelfPairPermissions(new UserPermissionsDto(dto.User, permCopy)).ConfigureAwait(false); + await Clients.User(dto.User.UID).Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(new UserData(UserUID), dto.Permissions)).ConfigureAwait(false); - if (otherEntry != null) + var newPairData = await _cacheService.GetPairData(UserUID, dto.User.UID).ConfigureAwait(false); + + if (newPairData.OwnPermissions.IsPaused != oldPairData.OwnPermissions.IsPaused) { - await Clients.User(dto.User.UID).Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(new UserData(UserUID), dto.Permissions)).ConfigureAwait(false); + var otherCharaIdent = await GetUserIdent(dto.User.UID).ConfigureAwait(false); + var otherPermissions = newPairData.OtherPermissions; - if (pauseChange) + if (UserCharaIdent == null || otherCharaIdent == null || (otherPermissions?.IsPaused ?? true)) return; + + if (newPairData.OwnPermissions.IsPaused) { - var otherCharaIdent = await GetUserIdent(pair.OtherUserUID).ConfigureAwait(false); - - if (UserCharaIdent == null || otherCharaIdent == null || otherEntry.IsPaused) return; - - if (dto.Permissions.IsPaused()) - { - await Clients.User(UserUID).Client_UserSendOffline(dto).ConfigureAwait(false); - await Clients.User(dto.User.UID).Client_UserSendOffline(new(new(UserUID))).ConfigureAwait(false); - } - else - { - await Clients.User(UserUID).Client_UserSendOnline(new(dto.User, otherCharaIdent)).ConfigureAwait(false); - await Clients.User(dto.User.UID).Client_UserSendOnline(new(new(UserUID), UserCharaIdent)).ConfigureAwait(false); - } + await Clients.User(UserUID).Client_UserSendOffline(dto).ConfigureAwait(false); + await Clients.User(dto.User.UID).Client_UserSendOffline(new(new(UserUID))).ConfigureAwait(false); + } + else + { + await Clients.User(UserUID).Client_UserSendOnline(new(dto.User, otherCharaIdent)).ConfigureAwait(false); + await Clients.User(dto.User.UID).Client_UserSendOnline(new(new(UserUID), UserCharaIdent)).ConfigureAwait(false); } } } @@ -498,6 +521,27 @@ public partial class MareHub await Clients.Caller.Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false); } + [Authorize(Policy = "Authenticated")] + public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissions) + { + _logger.LogCallInfo(MareHubLogger.Args(defaultPermissions)); + + var permissions = await _dbContext.UserDefaultPreferredPermissions.SingleAsync(u => u.UserUID == UserUID).ConfigureAwait(false); + + permissions.DisableGroupAnimations = defaultPermissions.DisableGroupAnimations; + permissions.DisableGroupSounds = defaultPermissions.DisableGroupSounds; + permissions.DisableGroupVFX = defaultPermissions.DisableGroupVFX; + permissions.DisableIndividualAnimations = defaultPermissions.DisableIndividualAnimations; + permissions.DisableIndividualSounds = defaultPermissions.DisableIndividualSounds; + permissions.DisableIndividualVFX = defaultPermissions.DisableIndividualVFX; + permissions.IndividualIsSticky = defaultPermissions.IndividualIsSticky; + + _dbContext.Update(permissions); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + + await Clients.Caller.Client_UserUpdateDefaultPermissions(defaultPermissions).ConfigureAwait(false); + } + [GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)] private static partial Regex GamePathRegex(); diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs index 19db0b7..9478b6b 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs @@ -7,10 +7,12 @@ using MareSynchronosServer.Utils; using MareSynchronosShared; using MareSynchronosShared.Data; using MareSynchronosShared.Metrics; +using MareSynchronosShared.Models; using MareSynchronosShared.Services; using MareSynchronosShared.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; using StackExchange.Redis.Extensions.Core.Abstractions; namespace MareSynchronosServer.Hubs; @@ -28,13 +30,14 @@ public partial class MareHub : Hub, IMareHub private readonly int _maxJoinedGroupsByUser; private readonly int _maxGroupUserCount; private readonly IRedisDatabase _redis; + private readonly UserPairCacheService _cacheService; private readonly Uri _fileServerAddress; private readonly Version _expectedClientVersion; public MareHub(MareMetrics mareMetrics, MareDbContext mareDbContext, ILogger logger, SystemInfoService systemInfoService, IConfigurationService configuration, IHttpContextAccessor contextAccessor, - IRedisDatabase redisDb) + IRedisDatabase redisDb, UserPairCacheService cacheService) { _mareMetrics = mareMetrics; _systemInfoService = systemInfoService; @@ -46,6 +49,7 @@ public partial class MareHub : Hub, IMareHub _expectedClientVersion = configuration.GetValueOrDefault(nameof(ServerConfiguration.ExpectedClientVersion), new Version(0, 0, 0)); _contextAccessor = contextAccessor; _redis = redisDb; + _cacheService = cacheService; _logger = new MareHubLogger(this, logger); _dbContext = mareDbContext; } @@ -64,7 +68,18 @@ public partial class MareHub : Hub, IMareHub await _dbContext.SaveChangesAsync().ConfigureAwait(false); await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, "Welcome to Mare Synchronos \"" + _shardName + "\", Current Online Users: " + _systemInfoService.SystemInfoDto.OnlineUsers).ConfigureAwait(false); - await SendOnlineToAllPairedUsers().ConfigureAwait(false); + + var defaultPermissions = await _dbContext.UserDefaultPreferredPermissions.SingleOrDefaultAsync(u => u.UserUID == UserUID).ConfigureAwait(false); + if (defaultPermissions == null) + { + defaultPermissions = new UserDefaultPreferredPermission() + { + UserUID = UserUID, + }; + + _dbContext.UserDefaultPreferredPermissions.Add(defaultPermissions); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + } return new ConnectionDto(new UserData(dbUser.UID, string.IsNullOrWhiteSpace(dbUser.Alias) ? null : dbUser.Alias)) { @@ -78,7 +93,17 @@ public partial class MareHub : Hub, IMareHub ShardName = _shardName, MaxGroupsJoinedByUser = _maxJoinedGroupsByUser, MaxGroupUserCount = _maxGroupUserCount, - FileServerAddress = _fileServerAddress + FileServerAddress = _fileServerAddress, + }, + DefaultPreferredPermissions = new DefaultPermissionsDto() + { + DisableGroupAnimations = defaultPermissions.DisableGroupAnimations, + DisableGroupSounds = defaultPermissions.DisableGroupSounds, + DisableGroupVFX = defaultPermissions.DisableGroupVFX, + DisableIndividualAnimations = defaultPermissions.DisableIndividualAnimations, + DisableIndividualSounds = defaultPermissions.DisableIndividualSounds, + DisableIndividualVFX = defaultPermissions.DisableIndividualVFX, + IndividualIsSticky = defaultPermissions.IndividualIsSticky, }, }; } @@ -119,11 +144,13 @@ public partial class MareHub : Hub, IMareHub _logger.LogCallWarning(MareHubLogger.Args(_contextAccessor.GetIpAddress(), exception.Message, exception.StackTrace)); await RemoveUserFromRedis().ConfigureAwait(false); + _cacheService.ClearCache(UserUID); await SendOfflineToAllPairedUsers().ConfigureAwait(false); _dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == UserUID)); await _dbContext.SaveChangesAsync().ConfigureAwait(false); + } catch { } diff --git a/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj b/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj index 0271300..38293d9 100644 --- a/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj +++ b/MareSynchronosServer/MareSynchronosServer/MareSynchronosServer.csproj @@ -21,20 +21,20 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/MareSynchronosServer/MareSynchronosServer/Program.cs b/MareSynchronosServer/MareSynchronosServer/Program.cs index d524a91..1fc4102 100644 --- a/MareSynchronosServer/MareSynchronosServer/Program.cs +++ b/MareSynchronosServer/MareSynchronosServer/Program.cs @@ -11,7 +11,7 @@ public class Program public static void Main(string[] args) { var hostBuilder = CreateHostBuilder(args); - var host = hostBuilder.Build(); + using var host = hostBuilder.Build(); using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; @@ -38,7 +38,7 @@ public class Program metrics.SetGaugeTo(MetricsAPI.GaugeUsersRegistered, context.Users.AsNoTracking().Count()); metrics.SetGaugeTo(MetricsAPI.GaugePairs, context.ClientPairs.AsNoTracking().Count()); - metrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, context.ClientPairs.AsNoTracking().Count(p => p.IsPaused)); + metrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, context.Permissions.AsNoTracking().Count(p => p.IsPaused)); } @@ -57,7 +57,7 @@ public class Program public static IHostBuilder CreateHostBuilder(string[] args) { - var loggerFactory = LoggerFactory.Create(builder => + using var loggerFactory = LoggerFactory.Create(builder => { builder.ClearProviders(); builder.AddConsole(); diff --git a/MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs b/MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs index 2df0c32..437170b 100644 --- a/MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs +++ b/MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs @@ -70,10 +70,9 @@ public class SystemInfoService : IHostedService, IDisposable _mareMetrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, onlineUsers); _mareMetrics.SetGaugeTo(MetricsAPI.GaugePairs, db.ClientPairs.AsNoTracking().Count()); - _mareMetrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, db.ClientPairs.AsNoTracking().Count(p => p.IsPaused)); + _mareMetrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, db.Permissions.AsNoTracking().Count(p => p.IsPaused)); _mareMetrics.SetGaugeTo(MetricsAPI.GaugeGroups, db.Groups.AsNoTracking().Count()); _mareMetrics.SetGaugeTo(MetricsAPI.GaugeGroupPairs, db.GroupPairs.AsNoTracking().Count()); - _mareMetrics.SetGaugeTo(MetricsAPI.GaugeGroupPairsPaused, db.GroupPairs.AsNoTracking().Count(p => p.IsPaused)); _mareMetrics.SetGaugeTo(MetricsAPI.GaugeUsersRegistered, db.Users.AsNoTracking().Count()); } } diff --git a/MareSynchronosServer/MareSynchronosServer/Services/UserPairCacheService.cs b/MareSynchronosServer/MareSynchronosServer/Services/UserPairCacheService.cs new file mode 100644 index 0000000..881ee61 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosServer/Services/UserPairCacheService.cs @@ -0,0 +1,194 @@ +using MareSynchronos.API.Data; +using MareSynchronosShared.Data; +using MareSynchronosShared.Models; +using Microsoft.EntityFrameworkCore; +using System.Collections.Concurrent; + +namespace MareSynchronosServer.Services; + +public class UserPairCacheService : IHostedService +{ + private readonly ConcurrentDictionary> _cache; + private readonly ILogger _logger; + private readonly IDbContextFactory _dbContextFactory; + private readonly ConcurrentQueue<(string? UID, string? OtherUID)> _staleUserData; + public UserPairCacheService(ILogger logger, IDbContextFactory dbContextFactory) + { + _logger = logger; + _dbContextFactory = dbContextFactory; + _staleUserData = new(); + _cache = new(StringComparer.OrdinalIgnoreCase); + } + + public void ClearCache(string uid) + { + _cache.TryRemove(uid, out _); + } + + public async Task> GetAllPairs(string uid) + { + await WaitForProcessing(uid).ConfigureAwait(false); + + if (!_cache.ContainsKey(uid)) + { + _logger.LogDebug("Building full cache: Did not find PairData for {uid}", uid); + + using var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + _cache[uid] = await BuildFullCache(dbContext, uid).ConfigureAwait(false); + } + + return _cache[uid]; + } + + public async Task GetPairData(string uid, string otheruid) + { + await WaitForProcessing(uid, otheruid).ConfigureAwait(false); + + if (!_cache.TryGetValue(uid, out var cachedInfos)) + { + _logger.LogDebug("Building full cache: Did not find PairData for {uid}:{otheruid}", uid, otheruid); + using var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + _cache[uid] = cachedInfos = await BuildFullCache(dbContext, uid).ConfigureAwait(false); + } + + if (!cachedInfos.TryGetValue(otheruid, out var info)) + { + _logger.LogDebug("Building individual cache: Did not find PairData for {uid}:{otheruid}", uid, otheruid); + using var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + info = await BuildIndividualCache(dbContext, uid, otheruid).ConfigureAwait(false); + _cache[uid][otheruid] = info; + } + + return info; + } + + public void MarkAsStale(string? uid, string? otheruid) + { + if (!_staleUserData.Any(u => string.Equals(u.UID, uid, StringComparison.Ordinal) + && string.Equals(u.OtherUID, otheruid, StringComparison.OrdinalIgnoreCase))) + { + _staleUserData.Enqueue((uid, otheruid)); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _ = ProcessStaleEntries(); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private async Task> BuildFullCache(MareDbContext dbContext, string uid) + { + var pairs = await dbContext.GetAllPairsForUser(uid).ToListAsync().ConfigureAwait(false); + + return pairs.GroupBy(g => g.OtherUserUID, StringComparer.Ordinal) + .ToDictionary(g => g.Key, g => + { + return new UserInfo(g.First().Alias, + g.SingleOrDefault(p => string.IsNullOrEmpty(p.GID))?.Synced ?? false, + g.Max(p => p.Synced), + g.Select(p => string.IsNullOrEmpty(p.GID) ? Constants.IndividualKeyword : p.GID).ToList(), + g.First().OwnPermissions, + g.First().OtherPermissions); + }, StringComparer.OrdinalIgnoreCase); + } + + private async Task BuildIndividualCache(MareDbContext dbContext, string uid, string otheruid) + { + var pairs = await dbContext.GetAllPairsForUser(uid).Where(u => u.OtherUserUID == otheruid).ToListAsync().ConfigureAwait(false); + + if (!pairs.Any()) return null; + + var groups = pairs.Select(g => g.GID).ToList(); + return new UserInfo(pairs[0].Alias, + pairs.SingleOrDefault(p => string.IsNullOrEmpty(p.GID))?.Synced ?? false, + pairs.Max(p => p.Synced), + pairs.Select(p => string.IsNullOrEmpty(p.GID) ? Constants.IndividualKeyword : p.GID).ToList(), + pairs[0].OwnPermissions, + pairs[0].OtherPermissions); + } + + private async Task ProcessStaleEntries() + { + while (true) + { + await Task.Delay(250).ConfigureAwait(false); + if (_staleUserData.Any()) + { + _logger.LogDebug("Processing Stale Entries"); + try + { + using var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false); + while (_staleUserData.TryPeek(out var staleUserPair)) + { + if (staleUserPair.UID == null) + { + foreach (var entry in _cache.Where(c => c.Value.ContainsKey(staleUserPair.OtherUID)).Select(k => k.Key).Distinct(StringComparer.Ordinal).ToList()) + { + _logger.LogDebug("UID is null; Building Individual Cache for {user}:{user2}", staleUserPair.UID, entry); + _staleUserData.Enqueue(new(staleUserPair.OtherUID, entry)); + } + } + else if (staleUserPair.OtherUID == null) + { + foreach (var entry in _cache.Where(c => c.Value.ContainsKey(staleUserPair.UID)).Select(k => k.Key).Distinct(StringComparer.Ordinal).ToList()) + { + _logger.LogDebug("OtherUID is null; Building Individual Cache for {user}:{user2}", staleUserPair.UID, entry); + _staleUserData.Enqueue(new(staleUserPair.UID, entry)); + } + } + else + { + if (_cache.ContainsKey(staleUserPair.UID)) + { + _logger.LogDebug("Building Individual Cache for {user}:{user2}", staleUserPair.UID, staleUserPair.OtherUID); + + var userInfo = await BuildIndividualCache(dbContext, staleUserPair.UID, staleUserPair.OtherUID).ConfigureAwait(false); + + if (userInfo == null) _cache[staleUserPair.UID].Remove(staleUserPair.OtherUID); + else _cache[staleUserPair.UID][staleUserPair.OtherUID] = userInfo; + + if (_cache.ContainsKey(staleUserPair.OtherUID)) + { + _logger.LogDebug("Building Individual Cache for {user}:{user2}", staleUserPair.OtherUID, staleUserPair.UID); + var otherUserInfo = await BuildIndividualCache(dbContext, staleUserPair.OtherUID, staleUserPair.UID).ConfigureAwait(false); + if (otherUserInfo == null) _cache[staleUserPair.OtherUID].Remove(staleUserPair.UID); + else _cache[staleUserPair.OtherUID][staleUserPair.UID] = otherUserInfo; + } + } + } + + _staleUserData.TryDequeue(out _); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during Stale entry processing"); + } + } + } + } + + private async Task WaitForProcessing(string uid) + { + while (_staleUserData.Any(u => string.Equals(u.UID, uid, StringComparison.Ordinal))) + { + await Task.Delay(50).ConfigureAwait(false); + } + } + + private async Task WaitForProcessing(string uid, string otheruid) + { + while (_staleUserData.Any(u => string.Equals(u.UID, uid, StringComparison.Ordinal) && string.Equals(u.OtherUID, otheruid, StringComparison.Ordinal))) + { + await Task.Delay(50).ConfigureAwait(false); + } + } + + public record UserInfo(string Alias, bool IndividuallyPaired, bool IsSynced, List GIDs, UserPermissionSet? OwnPermissions, UserPermissionSet? OtherPermissions); +} diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index 509357c..71bb0f8 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -5,8 +5,6 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.Authorization; using AspNetCoreRateLimit; using MareSynchronosShared.Data; -using MareSynchronosShared.Protos; -using Grpc.Net.Client.Configuration; using MareSynchronosShared.Metrics; using MareSynchronosServer.Services; using MareSynchronosShared.Utils; @@ -92,7 +90,9 @@ public class Startup services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddHostedService(provider => provider.GetService()); + services.AddHostedService(p => p.GetService()); // configure services based on main server status ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer); @@ -134,6 +134,7 @@ public class Startup .WithResolver(resolver); }); + // configure redis for SignalR var redisConnection = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty); signalRServiceBuilder.AddStackExchangeRedis(redisConnection, options => { }); @@ -185,6 +186,8 @@ public class Startup { services.AddSingleton(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddOptions(JwtBearerDefaults.AuthenticationScheme) .Configure>((options, config) => @@ -192,7 +195,7 @@ public class Startup options.TokenValidationParameters = new() { ValidateIssuer = false, - ValidateLifetime = false, + ValidateLifetime = true, ValidateAudience = false, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.GetValue(nameof(MareConfigurationAuthBase.Jwt)))), @@ -215,18 +218,24 @@ public class Startup { policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme); policy.RequireAuthenticatedUser(); + policy.AddRequirements(new ValidTokenRequirement()); }); options.AddPolicy("Identified", policy => { policy.AddRequirements(new UserRequirement(UserRequirements.Identified)); + policy.AddRequirements(new ValidTokenRequirement()); + }); options.AddPolicy("Admin", policy => { policy.AddRequirements(new UserRequirement(UserRequirements.Identified | UserRequirements.Administrator)); + policy.AddRequirements(new ValidTokenRequirement()); + }); options.AddPolicy("Moderator", policy => { policy.AddRequirements(new UserRequirement(UserRequirements.Identified | UserRequirements.Moderator | UserRequirements.Administrator)); + policy.AddRequirements(new ValidTokenRequirement()); }); options.AddPolicy("Internal", new AuthorizationPolicyBuilder().RequireClaim(MareClaimTypes.Internal, "true").Build()); }); @@ -243,6 +252,15 @@ public class Startup }).UseSnakeCaseNamingConvention(); options.EnableThreadSafetyChecks(false); }, mareConfig.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024)); + services.AddDbContextFactory(options => + { + options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder => + { + builder.MigrationsHistoryTable("_efmigrationshistory", "public"); + builder.MigrationsAssembly("MareSynchronosShared"); + }).UseSnakeCaseNamingConvention(); + options.EnableThreadSafetyChecks(false); + }); } private static void ConfigureMetrics(IServiceCollection services) @@ -267,8 +285,8 @@ public class Startup MetricsAPI.GaugeAvailableWorkerThreads, MetricsAPI.GaugeGroups, MetricsAPI.GaugeGroupPairs, - MetricsAPI.GaugeGroupPairsPaused, MetricsAPI.GaugeUsersRegistered, + MetricsAPI.GaugeAuthenticationCacheEntries, })); } diff --git a/MareSynchronosServer/MareSynchronosServer/Utils/Extensions.cs b/MareSynchronosServer/MareSynchronosServer/Utils/Extensions.cs index a9644ff..773cc0b 100644 --- a/MareSynchronosServer/MareSynchronosServer/Utils/Extensions.cs +++ b/MareSynchronosServer/MareSynchronosServer/Utils/Extensions.cs @@ -2,52 +2,73 @@ using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Extensions; using MareSynchronosShared.Models; +using static MareSynchronosServer.Services.UserPairCacheService; -namespace MareSynchronosServer.Utils +namespace MareSynchronosServer.Utils; + +public static class Extensions { - public static class Extensions + public static GroupData ToGroupData(this Group group) { - public static GroupData ToGroupData(this Group group) - { - return new GroupData(group.GID, group.Alias); - } + return new GroupData(group.GID, group.Alias); + } - public static UserData ToUserData(this GroupPair pair) - { - return new UserData(pair.GroupUser.UID, pair.GroupUser.Alias); - } + public static UserData ToUserData(this GroupPair pair) + { + return new UserData(pair.GroupUser.UID, pair.GroupUser.Alias); + } - public static UserData ToUserData(this User user) - { - return new UserData(user.UID, user.Alias); - } + public static UserData ToUserData(this User user) + { + return new UserData(user.UID, user.Alias); + } - public static GroupPermissions GetGroupPermissions(this Group group) - { - var permissions = GroupPermissions.NoneSet; - permissions.SetDisableAnimations(group.DisableAnimations); - permissions.SetDisableSounds(group.DisableSounds); - permissions.SetDisableInvites(!group.InvitesEnabled); - permissions.SetDisableVFX(group.DisableVFX); - return permissions; - } + public static IndividualPairStatus ToIndividualPairStatus(this UserInfo userInfo) + { + if (userInfo.IndividuallyPaired) return IndividualPairStatus.Bidirectional; + if (!userInfo.IndividuallyPaired && userInfo.GIDs.Contains(Constants.IndividualKeyword, StringComparer.Ordinal)) return IndividualPairStatus.OneSided; + return IndividualPairStatus.None; + } - public static GroupUserPermissions GetGroupPairPermissions(this GroupPair groupPair) - { - var permissions = GroupUserPermissions.NoneSet; - permissions.SetDisableAnimations(groupPair.DisableAnimations); - permissions.SetDisableSounds(groupPair.DisableSounds); - permissions.SetPaused(groupPair.IsPaused); - permissions.SetDisableVFX(groupPair.DisableVFX); - return permissions; - } + public static GroupPermissions ToEnum(this Group group) + { + var permissions = GroupPermissions.NoneSet; + permissions.SetPreferDisableAnimations(group.PreferDisableAnimations); + permissions.SetPreferDisableSounds(group.PreferDisableSounds); + permissions.SetPreferDisableVFX(group.PreferDisableVFX); + permissions.SetDisableInvites(!group.InvitesEnabled); + return permissions; + } - public static GroupUserInfo GetGroupPairUserInfo(this GroupPair groupPair) - { - var groupUserInfo = GroupUserInfo.None; - groupUserInfo.SetPinned(groupPair.IsPinned); - groupUserInfo.SetModerator(groupPair.IsModerator); - return groupUserInfo; - } + public static GroupUserPreferredPermissions ToEnum(this GroupPairPreferredPermission groupPair) + { + var permissions = GroupUserPreferredPermissions.NoneSet; + permissions.SetDisableAnimations(groupPair.DisableAnimations); + permissions.SetDisableSounds(groupPair.DisableSounds); + permissions.SetPaused(groupPair.IsPaused); + permissions.SetDisableVFX(groupPair.DisableVFX); + return permissions; + } + + public static GroupPairUserInfo ToEnum(this GroupPair groupPair) + { + var groupUserInfo = GroupPairUserInfo.None; + groupUserInfo.SetPinned(groupPair.IsPinned); + groupUserInfo.SetModerator(groupPair.IsModerator); + return groupUserInfo; + } + + public static UserPermissions ToUserPermissions(this UserPermissionSet? permissions, bool setSticky = false) + { + if (permissions == null) return UserPermissions.NoneSet; + + UserPermissions perm = UserPermissions.NoneSet; + perm.SetPaused(permissions.IsPaused); + perm.SetDisableAnimations(permissions.DisableAnimations); + perm.SetDisableSounds(permissions.DisableSounds); + perm.SetDisableVFX(permissions.DisableVFX); + if (setSticky) + perm.SetSticky(permissions.Sticky); + return perm; } } diff --git a/MareSynchronosServer/MareSynchronosServices/MareSynchronosServices.csproj b/MareSynchronosServer/MareSynchronosServices/MareSynchronosServices.csproj index 110a7ef..321995b 100644 --- a/MareSynchronosServer/MareSynchronosServices/MareSynchronosServices.csproj +++ b/MareSynchronosServer/MareSynchronosServices/MareSynchronosServices.csproj @@ -21,9 +21,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs b/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs index 7203e0d..47f975c 100644 --- a/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs +++ b/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs @@ -45,6 +45,11 @@ public class MareDbContext : DbContext public DbSet UserProfileData { get; set; } public DbSet UserProfileReports { get; set; } public DbSet Users { get; set; } + public DbSet Permissions { get; set; } + public DbSet GroupPairPreferredPermissions { get; set; } + public DbSet UserDefaultPreferredPermissions { get; set; } + + public IQueryable GetAllPairsForUser(string uid) => FromExpression(() => GetAllPairsForUser(uid)); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -77,5 +82,21 @@ public class MareDbContext : DbContext modelBuilder.Entity().ToTable("user_profile_data"); modelBuilder.Entity().HasKey(c => c.UserUID); modelBuilder.Entity().ToTable("user_profile_data_reports"); + modelBuilder.Entity().ToTable("user_permission_sets"); + modelBuilder.Entity().HasKey(u => new { u.UserUID, u.OtherUserUID }); + modelBuilder.Entity().HasIndex(c => c.UserUID); + modelBuilder.Entity().HasIndex(c => c.OtherUserUID); + modelBuilder.Entity().ToTable("group_pair_preferred_permissions"); + modelBuilder.Entity().HasKey(u => new { u.UserUID, u.GroupGID }); + modelBuilder.Entity().HasIndex(c => c.UserUID); + modelBuilder.Entity().HasIndex(c => c.GroupGID); + modelBuilder.Entity().ToTable("user_default_preferred_permissions"); + modelBuilder.Entity().HasKey(u => u.UserUID); + modelBuilder.Entity().HasIndex(u => u.UserUID); + modelBuilder.Entity().HasOne(u => u.User); + modelBuilder.HasDbFunction(typeof(MareDbContext).GetMethod(nameof(GetAllPairsForUser), new[] { typeof(string) })) + .HasName("get_all_pairs_for_user"); + modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().ToTable("user_permission_query", t => t.ExcludeFromMigrations()); } } \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj b/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj index f124b4b..b2269bc 100644 --- a/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj +++ b/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj @@ -20,13 +20,13 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -35,25 +35,25 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + - + diff --git a/MareSynchronosServer/MareSynchronosShared/Metrics/MetricsAPI.cs b/MareSynchronosServer/MareSynchronosShared/Metrics/MetricsAPI.cs index 7c41bbc..1e99676 100644 --- a/MareSynchronosServer/MareSynchronosShared/Metrics/MetricsAPI.cs +++ b/MareSynchronosServer/MareSynchronosShared/Metrics/MetricsAPI.cs @@ -19,9 +19,9 @@ public class MetricsAPI public const string CounterAuthenticationCacheHits = "mare_auth_requests_cachehit"; public const string CounterAuthenticationFailures = "mare_auth_requests_fail"; public const string CounterAuthenticationSuccesses = "mare_auth_requests_success"; + public const string GaugeAuthenticationCacheEntries = "mare_auth_cache"; public const string GaugeGroups = "mare_groups"; public const string GaugeGroupPairs = "mare_groups_pairs"; - public const string GaugeGroupPairsPaused = "mare_groups_pairs_paused"; public const string GaugeFilesUniquePastHour = "mare_files_unique_past_hour"; public const string GaugeFilesUniquePastHourSize = "mare_files_unique_past_hour_size"; public const string GaugeFilesUniquePastDay = "mare_files_unique_past_day"; diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20230924190113_permissions.Designer.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20230924190113_permissions.Designer.cs new file mode 100644 index 0000000..43d4158 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20230924190113_permissions.Designer.cs @@ -0,0 +1,805 @@ +// +using System; +using MareSynchronosShared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MareSynchronosServer.Migrations +{ + [DbContext(typeof(MareDbContext))] + [Migration("20230924190113_permissions")] + partial class permissions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => + { + b.Property("HashedKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("hashed_key"); + + b.Property("IsBanned") + .HasColumnType("boolean") + .HasColumnName("is_banned"); + + b.Property("PrimaryUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("primary_user_uid"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("HashedKey") + .HasName("pk_auth"); + + b.HasIndex("PrimaryUserUID") + .HasDatabaseName("ix_auth_primary_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_auth_user_uid"); + + b.ToTable("auth", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Banned", b => + { + b.Property("CharacterIdentification") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("character_identification"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("CharacterIdentification") + .HasName("pk_banned_users"); + + b.ToTable("banned_users", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.BannedRegistrations", b => + { + b.Property("DiscordIdOrLodestoneAuth") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("discord_id_or_lodestone_auth"); + + b.HasKey("DiscordIdOrLodestoneAuth") + .HasName("pk_banned_registrations"); + + b.ToTable("banned_registrations", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_client_pairs"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_client_pairs_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_client_pairs_user_uid"); + + b.ToTable("client_pairs", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.Property("UploadDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("upload_date"); + + b.Property("Uploaded") + .HasColumnType("boolean") + .HasColumnName("uploaded"); + + b.Property("UploaderUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uploader_uid"); + + b.HasKey("Hash") + .HasName("pk_file_caches"); + + b.HasIndex("UploaderUID") + .HasDatabaseName("ix_file_caches_uploader_uid"); + + b.ToTable("file_caches", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ForbiddenUploadEntry", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("ForbiddenBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("forbidden_by"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("Hash") + .HasName("pk_forbidden_upload_entries"); + + b.ToTable("forbidden_upload_entries", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Group", b => + { + b.Property("GID") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("gid"); + + b.Property("Alias") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("alias"); + + b.Property("HashedPassword") + .HasColumnType("text") + .HasColumnName("hashed_password"); + + b.Property("InvitesEnabled") + .HasColumnType("boolean") + .HasColumnName("invites_enabled"); + + b.Property("OwnerUID") + .HasColumnType("character varying(10)") + .HasColumnName("owner_uid"); + + b.Property("PreferDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_animations"); + + b.Property("PreferDisableSounds") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_sounds"); + + b.Property("PreferDisableVFX") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_vfx"); + + b.HasKey("GID") + .HasName("pk_groups"); + + b.HasIndex("OwnerUID") + .HasDatabaseName("ix_groups_owner_uid"); + + b.ToTable("groups", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("BannedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_user_uid"); + + b.Property("BannedByUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_by_uid"); + + b.Property("BannedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("banned_on"); + + b.Property("BannedReason") + .HasColumnType("text") + .HasColumnName("banned_reason"); + + b.HasKey("GroupGID", "BannedUserUID") + .HasName("pk_group_bans"); + + b.HasIndex("BannedByUID") + .HasDatabaseName("ix_group_bans_banned_by_uid"); + + b.HasIndex("BannedUserUID") + .HasDatabaseName("ix_group_bans_banned_user_uid"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_bans_group_gid"); + + b.ToTable("group_bans", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("GroupUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("group_user_uid"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("IsPinned") + .HasColumnType("boolean") + .HasColumnName("is_pinned"); + + b.HasKey("GroupGID", "GroupUserUID") + .HasName("pk_group_pairs"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pairs_group_gid"); + + b.HasIndex("GroupUserUID") + .HasDatabaseName("ix_group_pairs_group_user_uid"); + + b.ToTable("group_pairs", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.HasKey("UserUID", "GroupGID") + .HasName("pk_group_pair_preferred_permissions"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pair_preferred_permissions_group_gid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_group_pair_preferred_permissions_user_uid"); + + b.ToTable("group_pair_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("Invite") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("invite"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_date"); + + b.HasKey("GroupGID", "Invite") + .HasName("pk_group_temp_invites"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_temp_invites_group_gid"); + + b.HasIndex("Invite") + .HasDatabaseName("ix_group_temp_invites_invite"); + + b.ToTable("group_temp_invites", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => + { + b.Property("DiscordId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("discord_id"); + + b.Property("HashedLodestoneId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("hashed_lodestone_id"); + + b.Property("LodestoneAuthString") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("lodestone_auth_string"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("DiscordId") + .HasName("pk_lodestone_auth"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_lodestone_auth_user_uid"); + + b.ToTable("lodestone_auth", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.User", b => + { + b.Property("UID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uid"); + + b.Property("Alias") + .HasMaxLength(15) + .HasColumnType("character varying(15)") + .HasColumnName("alias"); + + b.Property("IsAdmin") + .HasColumnType("boolean") + .HasColumnName("is_admin"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("LastLoggedIn") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_logged_in"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UID") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => + { + b.Property("UserUID") + .HasColumnType("text") + .HasColumnName("user_uid"); + + b.Property("DisableGroupAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_group_animations"); + + b.Property("DisableGroupSounds") + .HasColumnType("boolean") + .HasColumnName("disable_group_sounds"); + + b.Property("DisableGroupVFX") + .HasColumnType("boolean") + .HasColumnName("disable_group_vfx"); + + b.Property("DisableIndividualAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_individual_animations"); + + b.Property("DisableIndividualSounds") + .HasColumnType("boolean") + .HasColumnName("disable_individual_sounds"); + + b.Property("DisableIndividualVFX") + .HasColumnType("boolean") + .HasColumnName("disable_individual_vfx"); + + b.Property("IndividualIsSticky") + .HasColumnType("boolean") + .HasColumnName("individual_is_sticky"); + + b.Property("UserUID1") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid1"); + + b.HasKey("UserUID") + .HasName("pk_user_default_preferred_permissions"); + + b.HasIndex("UserUID1") + .HasDatabaseName("ix_user_default_preferred_permissions_user_uid1"); + + b.ToTable("user_default_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.Property("Sticky") + .HasColumnType("boolean") + .HasColumnName("sticky"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_user_permission_sets"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_user_permission_sets_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_permission_sets_user_uid"); + + b.ToTable("user_permission_sets", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("Base64ProfileImage") + .HasColumnType("text") + .HasColumnName("base64profile_image"); + + b.Property("FlaggedForReport") + .HasColumnType("boolean") + .HasColumnName("flagged_for_report"); + + b.Property("IsNSFW") + .HasColumnType("boolean") + .HasColumnName("is_nsfw"); + + b.Property("ProfileDisabled") + .HasColumnType("boolean") + .HasColumnName("profile_disabled"); + + b.Property("UserDescription") + .HasColumnType("text") + .HasColumnName("user_description"); + + b.HasKey("UserUID") + .HasName("pk_user_profile_data"); + + b.ToTable("user_profile_data", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ReportDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("report_date"); + + b.Property("ReportReason") + .HasColumnType("text") + .HasColumnName("report_reason"); + + b.Property("ReportedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("reported_user_uid"); + + b.Property("ReportingUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("reporting_user_uid"); + + b.HasKey("Id") + .HasName("pk_user_profile_data_reports"); + + b.HasIndex("ReportedUserUID") + .HasDatabaseName("ix_user_profile_data_reports_reported_user_uid"); + + b.HasIndex("ReportingUserUID") + .HasDatabaseName("ix_user_profile_data_reports_reporting_user_uid"); + + b.ToTable("user_profile_data_reports", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => + { + b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") + .WithMany() + .HasForeignKey("PrimaryUserUID") + .HasConstraintName("fk_auth_users_primary_user_temp_id"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_auth_users_user_temp_id1"); + + b.Navigation("PrimaryUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => + { + b.HasOne("MareSynchronosShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_other_user_temp_id2"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_user_temp_id3"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => + { + b.HasOne("MareSynchronosShared.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderUID") + .HasConstraintName("fk_file_caches_users_uploader_uid"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Group", b => + { + b.HasOne("MareSynchronosShared.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerUID") + .HasConstraintName("fk_groups_users_owner_temp_id9"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => + { + b.HasOne("MareSynchronosShared.Models.User", "BannedBy") + .WithMany() + .HasForeignKey("BannedByUID") + .HasConstraintName("fk_group_bans_users_banned_by_temp_id5"); + + b.HasOne("MareSynchronosShared.Models.User", "BannedUser") + .WithMany() + .HasForeignKey("BannedUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_users_banned_user_temp_id6"); + + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_groups_group_temp_id"); + + b.Navigation("BannedBy"); + + b.Navigation("BannedUser"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_groups_group_temp_id2"); + + b.HasOne("MareSynchronosShared.Models.User", "GroupUser") + .WithMany() + .HasForeignKey("GroupUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_users_group_user_temp_id8"); + + b.Navigation("Group"); + + b.Navigation("GroupUser"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_groups_group_temp_id1"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_users_user_temp_id7"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_temp_invites_groups_group_gid"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_lodestone_auth_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID1") + .HasConstraintName("fk_user_default_preferred_permissions_users_user_temp_id13"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => + { + b.HasOne("MareSynchronosShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_other_user_uid"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_user_uid"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_profile_data_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => + { + b.HasOne("MareSynchronosShared.Models.User", "ReportedUser") + .WithMany() + .HasForeignKey("ReportedUserUID") + .HasConstraintName("fk_user_profile_data_reports_users_reported_user_uid"); + + b.HasOne("MareSynchronosShared.Models.User", "ReportingUser") + .WithMany() + .HasForeignKey("ReportingUserUID") + .HasConstraintName("fk_user_profile_data_reports_users_reporting_user_uid"); + + b.Navigation("ReportedUser"); + + b.Navigation("ReportingUser"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20230924190113_permissions.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20230924190113_permissions.cs new file mode 100644 index 0000000..80502d5 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20230924190113_permissions.cs @@ -0,0 +1,442 @@ +using MareSynchronosShared.Models; +using Microsoft.EntityFrameworkCore.Migrations; +using static System.Runtime.InteropServices.JavaScript.JSType; + +#nullable disable + +namespace MareSynchronosServer.Migrations +{ + /// + public partial class permissions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "group_pair_preferred_permissions", + columns: table => new + { + group_gid = table.Column(type: "character varying(20)", nullable: false), + user_uid = table.Column(type: "character varying(10)", nullable: false), + is_paused = table.Column(type: "boolean", nullable: false), + disable_animations = table.Column(type: "boolean", nullable: false), + disable_sounds = table.Column(type: "boolean", nullable: false), + disable_vfx = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_group_pair_preferred_permissions", x => new { x.user_uid, x.group_gid }); + table.ForeignKey( + name: "fk_group_pair_preferred_permissions_groups_group_temp_id1", + column: x => x.group_gid, + principalTable: "groups", + principalColumn: "gid", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_group_pair_preferred_permissions_users_user_temp_id7", + column: x => x.user_uid, + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "user_default_preferred_permissions", + columns: table => new + { + user_uid = table.Column(type: "text", nullable: false), + user_uid1 = table.Column(type: "character varying(10)", nullable: true), + disable_individual_animations = table.Column(type: "boolean", nullable: false), + disable_individual_sounds = table.Column(type: "boolean", nullable: false), + disable_individual_vfx = table.Column(type: "boolean", nullable: false), + disable_group_animations = table.Column(type: "boolean", nullable: false), + disable_group_sounds = table.Column(type: "boolean", nullable: false), + disable_group_vfx = table.Column(type: "boolean", nullable: false), + individual_is_sticky = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_user_default_preferred_permissions", x => x.user_uid); + table.ForeignKey( + name: "fk_user_default_preferred_permissions_users_user_temp_id13", + column: x => x.user_uid1, + principalTable: "users", + principalColumn: "uid"); + }); + + migrationBuilder.CreateTable( + name: "user_permission_sets", + columns: table => new + { + user_uid = table.Column(type: "character varying(10)", nullable: false), + other_user_uid = table.Column(type: "character varying(10)", nullable: false), + sticky = table.Column(type: "boolean", nullable: false), + is_paused = table.Column(type: "boolean", nullable: false), + disable_animations = table.Column(type: "boolean", nullable: false), + disable_vfx = table.Column(type: "boolean", nullable: false), + disable_sounds = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_user_permission_sets", x => new { x.user_uid, x.other_user_uid }); + table.ForeignKey( + name: "fk_user_permission_sets_users_other_user_uid", + column: x => x.other_user_uid, + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_user_permission_sets_users_user_uid", + column: x => x.user_uid, + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.Sql(@"insert into user_permission_sets + select user1.user_uid as user_uid, user1.other_user_uid as other_user_uid, + true, + user1.is_paused as is_paused, + user1.disable_animations as disable_animations, + user1.disable_sounds as disable_sounds, + user1.disable_vfx as disable_vfx + from client_pairs as user1;"); + + migrationBuilder.Sql(@"insert into user_permission_sets + select gp.group_user_uid, gp2.group_user_uid, + false, + bool_and(gp.is_paused), + bool_and(g.disable_animations or gp.disable_animations), + bool_and(g.disable_sounds or gp.disable_sounds), + bool_and(g.disable_vfx or gp.disable_vfx) + from group_pairs gp + left join group_pairs gp2 on gp2.group_gid = gp.group_gid + left join groups g on g.gid = gp2.group_gid + where gp2.group_user_uid <> gp.group_user_uid + group by gp.group_user_uid, gp2.group_user_uid + on conflict do nothing;"); + + migrationBuilder.Sql(@"insert into group_pair_preferred_permissions + select group_gid + , group_user_uid + , gp.is_paused + , gp.disable_animations or g.disable_animations as disable_animations + , gp.disable_sounds or g.disable_sounds as disable_sounds + , gp.disable_vfx or g.disable_vfx as disable_vfx + from group_pairs as gp + left join groups g on g.gid = gp.group_gid"); + + migrationBuilder.DropForeignKey( + name: "fk_group_pairs_groups_group_temp_id1", + table: "group_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_group_pairs_users_group_user_temp_id7", + table: "group_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_groups_users_owner_temp_id8", + table: "groups"); + + migrationBuilder.DropColumn( + name: "disable_animations", + table: "group_pairs"); + + migrationBuilder.DropColumn( + name: "disable_sounds", + table: "group_pairs"); + + migrationBuilder.DropColumn( + name: "disable_vfx", + table: "group_pairs"); + + migrationBuilder.DropColumn( + name: "is_paused", + table: "group_pairs"); + + migrationBuilder.DropColumn( + name: "allow_receiving_messages", + table: "client_pairs"); + + migrationBuilder.DropColumn( + name: "disable_animations", + table: "client_pairs"); + + migrationBuilder.DropColumn( + name: "disable_sounds", + table: "client_pairs"); + + migrationBuilder.DropColumn( + name: "disable_vfx", + table: "client_pairs"); + + migrationBuilder.DropColumn( + name: "is_paused", + table: "client_pairs"); + + migrationBuilder.RenameColumn( + name: "disable_vfx", + table: "groups", + newName: "prefer_disable_vfx"); + + migrationBuilder.RenameColumn( + name: "disable_sounds", + table: "groups", + newName: "prefer_disable_sounds"); + + migrationBuilder.RenameColumn( + name: "disable_animations", + table: "groups", + newName: "prefer_disable_animations"); + + migrationBuilder.CreateIndex( + name: "ix_group_pair_preferred_permissions_group_gid", + table: "group_pair_preferred_permissions", + column: "group_gid"); + + migrationBuilder.CreateIndex( + name: "ix_group_pair_preferred_permissions_user_uid", + table: "group_pair_preferred_permissions", + column: "user_uid"); + + migrationBuilder.CreateIndex( + name: "ix_user_default_preferred_permissions_user_uid1", + table: "user_default_preferred_permissions", + column: "user_uid1"); + + migrationBuilder.CreateIndex( + name: "ix_user_permission_sets_other_user_uid", + table: "user_permission_sets", + column: "other_user_uid"); + + migrationBuilder.CreateIndex( + name: "ix_user_permission_sets_user_uid", + table: "user_permission_sets", + column: "user_uid"); + + migrationBuilder.AddForeignKey( + name: "fk_group_pairs_groups_group_temp_id2", + table: "group_pairs", + column: "group_gid", + principalTable: "groups", + principalColumn: "gid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_group_pairs_users_group_user_temp_id8", + table: "group_pairs", + column: "group_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_groups_users_owner_temp_id9", + table: "groups", + column: "owner_uid", + principalTable: "users", + principalColumn: "uid"); + + migrationBuilder.Sql(@"create function get_all_pairs_for_user(req_uid text) +returns table( + user_uid varchar(10) + ,other_user_uid varchar(10) + ,alias varchar(15) + ,gid varchar(20) + ,synced bool + ,ownperm_is_paused bool + ,ownperm_sticky bool + ,ownperm_disable_animations bool + ,ownperm_disable_sounds bool + ,ownperm_disable_vfx bool + ,otherperm_is_paused bool + ,otherperm_disable_animations bool + ,otherperm_disable_sounds bool + ,otherperm_disable_vfx bool) +as +$$ +begin +return query( +WITH query1 AS ( + SELECT user1.user_uid AS user_uid + ,user1.other_user_uid AS other_user_uid + ,NULL AS gid + ,NOT (user2 IS NULL) AS synced + FROM client_pairs AS user1 + LEFT JOIN client_pairs user2 ON user1.user_uid = user2.other_user_uid + AND user2.user_uid = user1.other_user_uid + WHERE user1.user_uid = req_uid +), +query2 AS ( + SELECT gp.group_user_uid + ,gp2.group_user_uid + ,gp.group_gid + ,true + FROM group_pairs gp + LEFT JOIN group_pairs gp2 ON gp2.group_gid = gp.group_gid + WHERE gp.group_user_uid = req_uid + AND gp2.group_user_uid <> req_uid + AND gp2.group_gid = gp.group_gid +) + +SELECT pairs.user_uid + ,pairs.other_user_uid + ,u.alias + ,cast(pairs.gid as varchar(20)) + ,pairs.synced + ,ownperm.is_paused + ,ownperm.sticky + ,ownperm.disable_animations + ,ownperm.disable_sounds + ,ownperm.disable_vfx + ,otherperm.is_paused + ,otherperm.disable_animations + ,otherperm.disable_sounds + ,otherperm.disable_vfx +FROM (SELECT * FROM query1 + union all + SELECT * FROM query2) AS pairs +LEFT JOIN users AS u ON pairs.other_user_uid = u.uid +LEFT JOIN user_permission_sets AS ownperm ON pairs.user_uid = ownperm.user_uid + AND pairs.other_user_uid = ownperm.other_user_uid +LEFT JOIN user_permission_sets AS otherperm ON pairs.user_uid = otherperm.other_user_uid + AND pairs.other_user_uid = otherperm.user_uid +WHERE pairs.user_uid = req_uid + AND u.uid = pairs.other_user_uid + AND ( + (ownperm.user_uid = req_uid) + OR (otherperm.other_user_uid = req_uid) + ) +); +end; +$$ +language plpgsql;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_group_pairs_groups_group_temp_id2", + table: "group_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_group_pairs_users_group_user_temp_id8", + table: "group_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_groups_users_owner_temp_id9", + table: "groups"); + + migrationBuilder.DropTable( + name: "group_pair_preferred_permissions"); + + migrationBuilder.DropTable( + name: "user_default_preferred_permissions"); + + migrationBuilder.DropTable( + name: "user_permission_sets"); + + migrationBuilder.RenameColumn( + name: "prefer_disable_vfx", + table: "groups", + newName: "disable_vfx"); + + migrationBuilder.RenameColumn( + name: "prefer_disable_sounds", + table: "groups", + newName: "disable_sounds"); + + migrationBuilder.RenameColumn( + name: "prefer_disable_animations", + table: "groups", + newName: "disable_animations"); + + migrationBuilder.AddColumn( + name: "disable_animations", + table: "group_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "disable_sounds", + table: "group_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "disable_vfx", + table: "group_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "is_paused", + table: "group_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "allow_receiving_messages", + table: "client_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "disable_animations", + table: "client_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "disable_sounds", + table: "client_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "disable_vfx", + table: "client_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "is_paused", + table: "client_pairs", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddForeignKey( + name: "fk_group_pairs_groups_group_temp_id1", + table: "group_pairs", + column: "group_gid", + principalTable: "groups", + principalColumn: "gid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_group_pairs_users_group_user_temp_id7", + table: "group_pairs", + column: "group_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_groups_users_owner_temp_id8", + table: "groups", + column: "owner_uid", + principalTable: "users", + principalColumn: "uid"); + } + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20230926212023_AlterPermissions.Designer.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20230926212023_AlterPermissions.Designer.cs new file mode 100644 index 0000000..b201ee4 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20230926212023_AlterPermissions.Designer.cs @@ -0,0 +1,868 @@ +// +using System; +using MareSynchronosShared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MareSynchronosServer.Migrations +{ + [DbContext(typeof(MareDbContext))] + [Migration("20230926212023_AlterPermissions")] + partial class AlterPermissions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => + { + b.Property("HashedKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("hashed_key"); + + b.Property("IsBanned") + .HasColumnType("boolean") + .HasColumnName("is_banned"); + + b.Property("PrimaryUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("primary_user_uid"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("HashedKey") + .HasName("pk_auth"); + + b.HasIndex("PrimaryUserUID") + .HasDatabaseName("ix_auth_primary_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_auth_user_uid"); + + b.ToTable("auth", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Banned", b => + { + b.Property("CharacterIdentification") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("character_identification"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("CharacterIdentification") + .HasName("pk_banned_users"); + + b.ToTable("banned_users", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.BannedRegistrations", b => + { + b.Property("DiscordIdOrLodestoneAuth") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("discord_id_or_lodestone_auth"); + + b.HasKey("DiscordIdOrLodestoneAuth") + .HasName("pk_banned_registrations"); + + b.ToTable("banned_registrations", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_client_pairs"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_client_pairs_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_client_pairs_user_uid"); + + b.ToTable("client_pairs", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.Property("UploadDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("upload_date"); + + b.Property("Uploaded") + .HasColumnType("boolean") + .HasColumnName("uploaded"); + + b.Property("UploaderUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uploader_uid"); + + b.HasKey("Hash") + .HasName("pk_file_caches"); + + b.HasIndex("UploaderUID") + .HasDatabaseName("ix_file_caches_uploader_uid"); + + b.ToTable("file_caches", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ForbiddenUploadEntry", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("ForbiddenBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("forbidden_by"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("Hash") + .HasName("pk_forbidden_upload_entries"); + + b.ToTable("forbidden_upload_entries", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Group", b => + { + b.Property("GID") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("gid"); + + b.Property("Alias") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("alias"); + + b.Property("HashedPassword") + .HasColumnType("text") + .HasColumnName("hashed_password"); + + b.Property("InvitesEnabled") + .HasColumnType("boolean") + .HasColumnName("invites_enabled"); + + b.Property("OwnerUID") + .HasColumnType("character varying(10)") + .HasColumnName("owner_uid"); + + b.Property("PreferDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_animations"); + + b.Property("PreferDisableSounds") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_sounds"); + + b.Property("PreferDisableVFX") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_vfx"); + + b.HasKey("GID") + .HasName("pk_groups"); + + b.HasIndex("OwnerUID") + .HasDatabaseName("ix_groups_owner_uid"); + + b.ToTable("groups", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("BannedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_user_uid"); + + b.Property("BannedByUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_by_uid"); + + b.Property("BannedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("banned_on"); + + b.Property("BannedReason") + .HasColumnType("text") + .HasColumnName("banned_reason"); + + b.HasKey("GroupGID", "BannedUserUID") + .HasName("pk_group_bans"); + + b.HasIndex("BannedByUID") + .HasDatabaseName("ix_group_bans_banned_by_uid"); + + b.HasIndex("BannedUserUID") + .HasDatabaseName("ix_group_bans_banned_user_uid"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_bans_group_gid"); + + b.ToTable("group_bans", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("GroupUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("group_user_uid"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("IsPinned") + .HasColumnType("boolean") + .HasColumnName("is_pinned"); + + b.HasKey("GroupGID", "GroupUserUID") + .HasName("pk_group_pairs"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pairs_group_gid"); + + b.HasIndex("GroupUserUID") + .HasDatabaseName("ix_group_pairs_group_user_uid"); + + b.ToTable("group_pairs", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.HasKey("UserUID", "GroupGID") + .HasName("pk_group_pair_preferred_permissions"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pair_preferred_permissions_group_gid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_group_pair_preferred_permissions_user_uid"); + + b.ToTable("group_pair_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("Invite") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("invite"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_date"); + + b.HasKey("GroupGID", "Invite") + .HasName("pk_group_temp_invites"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_temp_invites_group_gid"); + + b.HasIndex("Invite") + .HasDatabaseName("ix_group_temp_invites_invite"); + + b.ToTable("group_temp_invites", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => + { + b.Property("DiscordId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("discord_id"); + + b.Property("HashedLodestoneId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("hashed_lodestone_id"); + + b.Property("LodestoneAuthString") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("lodestone_auth_string"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("DiscordId") + .HasName("pk_lodestone_auth"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_lodestone_auth_user_uid"); + + b.ToTable("lodestone_auth", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.User", b => + { + b.Property("UID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uid"); + + b.Property("Alias") + .HasMaxLength(15) + .HasColumnType("character varying(15)") + .HasColumnName("alias"); + + b.Property("IsAdmin") + .HasColumnType("boolean") + .HasColumnName("is_admin"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("LastLoggedIn") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_logged_in"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UID") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("DisableGroupAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_group_animations"); + + b.Property("DisableGroupSounds") + .HasColumnType("boolean") + .HasColumnName("disable_group_sounds"); + + b.Property("DisableGroupVFX") + .HasColumnType("boolean") + .HasColumnName("disable_group_vfx"); + + b.Property("DisableIndividualAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_individual_animations"); + + b.Property("DisableIndividualSounds") + .HasColumnType("boolean") + .HasColumnName("disable_individual_sounds"); + + b.Property("DisableIndividualVFX") + .HasColumnType("boolean") + .HasColumnName("disable_individual_vfx"); + + b.Property("IndividualIsSticky") + .HasColumnType("boolean") + .HasColumnName("individual_is_sticky"); + + b.HasKey("UserUID") + .HasName("pk_user_default_preferred_permissions"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_default_preferred_permissions_user_uid"); + + b.ToTable("user_default_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionQuery", b => + { + b.Property("Alias") + .HasColumnType("text") + .HasColumnName("alias"); + + b.Property("GID") + .HasColumnType("text") + .HasColumnName("gid"); + + b.Property("OtherUserUID") + .HasColumnType("text") + .HasColumnName("other_user_uid"); + + b.Property("OtherpermDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("otherperm_disable_animations"); + + b.Property("OtherpermDisableSounds") + .HasColumnType("boolean") + .HasColumnName("otherperm_disable_sounds"); + + b.Property("OtherpermDisableVFX") + .HasColumnType("boolean") + .HasColumnName("otherperm_disable_vfx"); + + b.Property("OtherpermIsPaused") + .HasColumnType("boolean") + .HasColumnName("otherperm_is_paused"); + + b.Property("OwnPermSticky") + .HasColumnType("boolean") + .HasColumnName("own_perm_sticky"); + + b.Property("OwnpermDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("ownperm_disable_animations"); + + b.Property("OwnpermDisableSounds") + .HasColumnType("boolean") + .HasColumnName("ownperm_disable_sounds"); + + b.Property("OwnpermDisableVFX") + .HasColumnType("boolean") + .HasColumnName("ownperm_disable_vfx"); + + b.Property("OwnpermIsPaused") + .HasColumnType("boolean") + .HasColumnName("ownperm_is_paused"); + + b.Property("Synced") + .HasColumnType("boolean") + .HasColumnName("synced"); + + b.Property("UserUID") + .HasColumnType("text") + .HasColumnName("user_uid"); + + b.ToTable("user_permission_query", null, t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.Property("Sticky") + .HasColumnType("boolean") + .HasColumnName("sticky"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_user_permission_sets"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_user_permission_sets_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_permission_sets_user_uid"); + + b.ToTable("user_permission_sets", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("Base64ProfileImage") + .HasColumnType("text") + .HasColumnName("base64profile_image"); + + b.Property("FlaggedForReport") + .HasColumnType("boolean") + .HasColumnName("flagged_for_report"); + + b.Property("IsNSFW") + .HasColumnType("boolean") + .HasColumnName("is_nsfw"); + + b.Property("ProfileDisabled") + .HasColumnType("boolean") + .HasColumnName("profile_disabled"); + + b.Property("UserDescription") + .HasColumnType("text") + .HasColumnName("user_description"); + + b.HasKey("UserUID") + .HasName("pk_user_profile_data"); + + b.ToTable("user_profile_data", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ReportDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("report_date"); + + b.Property("ReportReason") + .HasColumnType("text") + .HasColumnName("report_reason"); + + b.Property("ReportedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("reported_user_uid"); + + b.Property("ReportingUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("reporting_user_uid"); + + b.HasKey("Id") + .HasName("pk_user_profile_data_reports"); + + b.HasIndex("ReportedUserUID") + .HasDatabaseName("ix_user_profile_data_reports_reported_user_uid"); + + b.HasIndex("ReportingUserUID") + .HasDatabaseName("ix_user_profile_data_reports_reporting_user_uid"); + + b.ToTable("user_profile_data_reports", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => + { + b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") + .WithMany() + .HasForeignKey("PrimaryUserUID") + .HasConstraintName("fk_auth_users_primary_user_temp_id"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_auth_users_user_temp_id1"); + + b.Navigation("PrimaryUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => + { + b.HasOne("MareSynchronosShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_other_user_temp_id2"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_user_temp_id3"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => + { + b.HasOne("MareSynchronosShared.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderUID") + .HasConstraintName("fk_file_caches_users_uploader_uid"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Group", b => + { + b.HasOne("MareSynchronosShared.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerUID") + .HasConstraintName("fk_groups_users_owner_temp_id9"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => + { + b.HasOne("MareSynchronosShared.Models.User", "BannedBy") + .WithMany() + .HasForeignKey("BannedByUID") + .HasConstraintName("fk_group_bans_users_banned_by_temp_id5"); + + b.HasOne("MareSynchronosShared.Models.User", "BannedUser") + .WithMany() + .HasForeignKey("BannedUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_users_banned_user_temp_id6"); + + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_groups_group_temp_id"); + + b.Navigation("BannedBy"); + + b.Navigation("BannedUser"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_groups_group_temp_id2"); + + b.HasOne("MareSynchronosShared.Models.User", "GroupUser") + .WithMany() + .HasForeignKey("GroupUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_users_group_user_temp_id8"); + + b.Navigation("Group"); + + b.Navigation("GroupUser"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_groups_group_temp_id1"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_users_user_temp_id7"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_temp_invites_groups_group_gid"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_lodestone_auth_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_default_preferred_permissions_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => + { + b.HasOne("MareSynchronosShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_other_user_uid"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_user_uid"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_profile_data_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => + { + b.HasOne("MareSynchronosShared.Models.User", "ReportedUser") + .WithMany() + .HasForeignKey("ReportedUserUID") + .HasConstraintName("fk_user_profile_data_reports_users_reported_user_uid"); + + b.HasOne("MareSynchronosShared.Models.User", "ReportingUser") + .WithMany() + .HasForeignKey("ReportingUserUID") + .HasConstraintName("fk_user_profile_data_reports_users_reporting_user_uid"); + + b.Navigation("ReportedUser"); + + b.Navigation("ReportingUser"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20230926212023_AlterPermissions.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20230926212023_AlterPermissions.cs new file mode 100644 index 0000000..5bee700 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20230926212023_AlterPermissions.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MareSynchronosServer.Migrations +{ + /// + public partial class AlterPermissions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_user_default_preferred_permissions_users_user_temp_id13", + table: "user_default_preferred_permissions"); + + migrationBuilder.DropIndex( + name: "ix_user_default_preferred_permissions_user_uid1", + table: "user_default_preferred_permissions"); + + migrationBuilder.DropColumn( + name: "user_uid1", + table: "user_default_preferred_permissions"); + + migrationBuilder.AlterColumn( + name: "user_uid", + table: "user_default_preferred_permissions", + type: "character varying(10)", + maxLength: 10, + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.CreateIndex( + name: "ix_user_default_preferred_permissions_user_uid", + table: "user_default_preferred_permissions", + column: "user_uid"); + + migrationBuilder.AddForeignKey( + name: "fk_user_default_preferred_permissions_users_user_uid", + table: "user_default_preferred_permissions", + column: "user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_user_default_preferred_permissions_users_user_uid", + table: "user_default_preferred_permissions"); + + migrationBuilder.DropIndex( + name: "ix_user_default_preferred_permissions_user_uid", + table: "user_default_preferred_permissions"); + + migrationBuilder.AlterColumn( + name: "user_uid", + table: "user_default_preferred_permissions", + type: "text", + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(10)", + oldMaxLength: 10); + + migrationBuilder.AddColumn( + name: "user_uid1", + table: "user_default_preferred_permissions", + type: "character varying(10)", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_user_default_preferred_permissions_user_uid1", + table: "user_default_preferred_permissions", + column: "user_uid1"); + + migrationBuilder.AddForeignKey( + name: "fk_user_default_preferred_permissions_users_user_temp_id13", + table: "user_default_preferred_permissions", + column: "user_uid1", + principalTable: "users", + principalColumn: "uid"); + } + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs index b648e1e..474fc46 100644 --- a/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MareSynchronosServer.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.4") + .HasAnnotation("ProductVersion", "7.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -101,26 +101,6 @@ namespace MareSynchronosServer.Migrations .HasColumnType("character varying(10)") .HasColumnName("other_user_uid"); - b.Property("AllowReceivingMessages") - .HasColumnType("boolean") - .HasColumnName("allow_receiving_messages"); - - b.Property("DisableAnimations") - .HasColumnType("boolean") - .HasColumnName("disable_animations"); - - b.Property("DisableSounds") - .HasColumnType("boolean") - .HasColumnName("disable_sounds"); - - b.Property("DisableVFX") - .HasColumnType("boolean") - .HasColumnName("disable_vfx"); - - b.Property("IsPaused") - .HasColumnType("boolean") - .HasColumnName("is_paused"); - b.Property("Timestamp") .IsConcurrencyToken() .ValueGeneratedOnAddOrUpdate() @@ -214,18 +194,6 @@ namespace MareSynchronosServer.Migrations .HasColumnType("character varying(50)") .HasColumnName("alias"); - b.Property("DisableAnimations") - .HasColumnType("boolean") - .HasColumnName("disable_animations"); - - b.Property("DisableSounds") - .HasColumnType("boolean") - .HasColumnName("disable_sounds"); - - b.Property("DisableVFX") - .HasColumnType("boolean") - .HasColumnName("disable_vfx"); - b.Property("HashedPassword") .HasColumnType("text") .HasColumnName("hashed_password"); @@ -238,6 +206,18 @@ namespace MareSynchronosServer.Migrations .HasColumnType("character varying(10)") .HasColumnName("owner_uid"); + b.Property("PreferDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_animations"); + + b.Property("PreferDisableSounds") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_sounds"); + + b.Property("PreferDisableVFX") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_vfx"); + b.HasKey("GID") .HasName("pk_groups"); @@ -294,26 +274,10 @@ namespace MareSynchronosServer.Migrations .HasColumnType("character varying(10)") .HasColumnName("group_user_uid"); - b.Property("DisableAnimations") - .HasColumnType("boolean") - .HasColumnName("disable_animations"); - - b.Property("DisableSounds") - .HasColumnType("boolean") - .HasColumnName("disable_sounds"); - - b.Property("DisableVFX") - .HasColumnType("boolean") - .HasColumnName("disable_vfx"); - b.Property("IsModerator") .HasColumnType("boolean") .HasColumnName("is_moderator"); - b.Property("IsPaused") - .HasColumnType("boolean") - .HasColumnName("is_paused"); - b.Property("IsPinned") .HasColumnType("boolean") .HasColumnName("is_pinned"); @@ -330,6 +294,44 @@ namespace MareSynchronosServer.Migrations b.ToTable("group_pairs", (string)null); }); + modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.HasKey("UserUID", "GroupGID") + .HasName("pk_group_pair_preferred_permissions"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pair_preferred_permissions_group_gid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_group_pair_preferred_permissions_user_uid"); + + b.ToTable("group_pair_preferred_permissions", (string)null); + }); + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => { b.Property("GroupGID") @@ -427,6 +429,156 @@ namespace MareSynchronosServer.Migrations b.ToTable("users", (string)null); }); + modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("DisableGroupAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_group_animations"); + + b.Property("DisableGroupSounds") + .HasColumnType("boolean") + .HasColumnName("disable_group_sounds"); + + b.Property("DisableGroupVFX") + .HasColumnType("boolean") + .HasColumnName("disable_group_vfx"); + + b.Property("DisableIndividualAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_individual_animations"); + + b.Property("DisableIndividualSounds") + .HasColumnType("boolean") + .HasColumnName("disable_individual_sounds"); + + b.Property("DisableIndividualVFX") + .HasColumnType("boolean") + .HasColumnName("disable_individual_vfx"); + + b.Property("IndividualIsSticky") + .HasColumnType("boolean") + .HasColumnName("individual_is_sticky"); + + b.HasKey("UserUID") + .HasName("pk_user_default_preferred_permissions"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_default_preferred_permissions_user_uid"); + + b.ToTable("user_default_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionQuery", b => + { + b.Property("Alias") + .HasColumnType("text") + .HasColumnName("alias"); + + b.Property("GID") + .HasColumnType("text") + .HasColumnName("gid"); + + b.Property("OtherUserUID") + .HasColumnType("text") + .HasColumnName("other_user_uid"); + + b.Property("OtherpermDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("otherperm_disable_animations"); + + b.Property("OtherpermDisableSounds") + .HasColumnType("boolean") + .HasColumnName("otherperm_disable_sounds"); + + b.Property("OtherpermDisableVFX") + .HasColumnType("boolean") + .HasColumnName("otherperm_disable_vfx"); + + b.Property("OtherpermIsPaused") + .HasColumnType("boolean") + .HasColumnName("otherperm_is_paused"); + + b.Property("OwnPermSticky") + .HasColumnType("boolean") + .HasColumnName("own_perm_sticky"); + + b.Property("OwnpermDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("ownperm_disable_animations"); + + b.Property("OwnpermDisableSounds") + .HasColumnType("boolean") + .HasColumnName("ownperm_disable_sounds"); + + b.Property("OwnpermDisableVFX") + .HasColumnType("boolean") + .HasColumnName("ownperm_disable_vfx"); + + b.Property("OwnpermIsPaused") + .HasColumnType("boolean") + .HasColumnName("ownperm_is_paused"); + + b.Property("Synced") + .HasColumnType("boolean") + .HasColumnName("synced"); + + b.Property("UserUID") + .HasColumnType("text") + .HasColumnName("user_uid"); + + b.ToTable("user_permission_query", null, t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.Property("Sticky") + .HasColumnType("boolean") + .HasColumnName("sticky"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_user_permission_sets"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_user_permission_sets_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_permission_sets_user_uid"); + + b.ToTable("user_permission_sets", (string)null); + }); + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => { b.Property("UserUID") @@ -549,7 +701,7 @@ namespace MareSynchronosServer.Migrations b.HasOne("MareSynchronosShared.Models.User", "Owner") .WithMany() .HasForeignKey("OwnerUID") - .HasConstraintName("fk_groups_users_owner_temp_id8"); + .HasConstraintName("fk_groups_users_owner_temp_id9"); b.Navigation("Owner"); }); @@ -589,20 +741,41 @@ namespace MareSynchronosServer.Migrations .HasForeignKey("GroupGID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_group_pairs_groups_group_temp_id1"); + .HasConstraintName("fk_group_pairs_groups_group_temp_id2"); b.HasOne("MareSynchronosShared.Models.User", "GroupUser") .WithMany() .HasForeignKey("GroupUserUID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_group_pairs_users_group_user_temp_id7"); + .HasConstraintName("fk_group_pairs_users_group_user_temp_id8"); b.Navigation("Group"); b.Navigation("GroupUser"); }); + modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_groups_group_temp_id1"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_users_user_temp_id7"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => { b.HasOne("MareSynchronosShared.Models.Group", "Group") @@ -625,6 +798,39 @@ namespace MareSynchronosServer.Migrations b.Navigation("User"); }); + modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_default_preferred_permissions_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => + { + b.HasOne("MareSynchronosShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_other_user_uid"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_user_uid"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => { b.HasOne("MareSynchronosShared.Models.User", "User") diff --git a/MareSynchronosServer/MareSynchronosShared/Models/ClientPair.cs b/MareSynchronosServer/MareSynchronosShared/Models/ClientPair.cs index 694ccdc..a20615f 100644 --- a/MareSynchronosServer/MareSynchronosShared/Models/ClientPair.cs +++ b/MareSynchronosServer/MareSynchronosShared/Models/ClientPair.cs @@ -10,11 +10,6 @@ public class ClientPair [MaxLength(10)] public string OtherUserUID { get; set; } public User OtherUser { get; set; } - public bool IsPaused { get; set; } - public bool AllowReceivingMessages { get; set; } = false; [Timestamp] public byte[] Timestamp { get; set; } - public bool DisableSounds { get; set; } = false; - public bool DisableAnimations { get; set; } = false; - public bool DisableVFX { get; set; } = false; } diff --git a/MareSynchronosServer/MareSynchronosShared/Models/Group.cs b/MareSynchronosServer/MareSynchronosShared/Models/Group.cs index 72f35d7..860bce2 100644 --- a/MareSynchronosServer/MareSynchronosShared/Models/Group.cs +++ b/MareSynchronosServer/MareSynchronosShared/Models/Group.cs @@ -13,7 +13,7 @@ public class Group public string Alias { get; set; } public bool InvitesEnabled { get; set; } public string HashedPassword { get; set; } - public bool DisableSounds { get; set; } - public bool DisableAnimations { get; set; } - public bool DisableVFX { get; set; } + public bool PreferDisableSounds { get; set; } + public bool PreferDisableAnimations { get; set; } + public bool PreferDisableVFX { get; set; } } diff --git a/MareSynchronosServer/MareSynchronosShared/Models/GroupPair.cs b/MareSynchronosServer/MareSynchronosShared/Models/GroupPair.cs index 57abe80..055e06a 100644 --- a/MareSynchronosServer/MareSynchronosShared/Models/GroupPair.cs +++ b/MareSynchronosServer/MareSynchronosShared/Models/GroupPair.cs @@ -6,10 +6,6 @@ public class GroupPair public Group Group { get; set; } public string GroupUserUID { get; set; } public User GroupUser { get; set; } - public bool IsPaused { get; set; } public bool IsPinned { get; set; } public bool IsModerator { get; set; } - public bool DisableSounds { get; set; } - public bool DisableAnimations { get; set; } - public bool DisableVFX { get; set; } } diff --git a/MareSynchronosServer/MareSynchronosShared/Models/GroupPairPreferredPermission.cs b/MareSynchronosServer/MareSynchronosShared/Models/GroupPairPreferredPermission.cs new file mode 100644 index 0000000..81f6b4b --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Models/GroupPairPreferredPermission.cs @@ -0,0 +1,13 @@ +namespace MareSynchronosShared.Models; + +public class GroupPairPreferredPermission +{ + public string GroupGID { get; set; } + public Group Group { get; set; } + public string UserUID { get; set; } + public User User { get; set; } + public bool IsPaused { get; set; } + public bool DisableAnimations { get; set; } + public bool DisableSounds { get; set; } + public bool DisableVFX { get; set; } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Models/UserDefaultPreferredPermission.cs b/MareSynchronosServer/MareSynchronosShared/Models/UserDefaultPreferredPermission.cs new file mode 100644 index 0000000..c27d03d --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Models/UserDefaultPreferredPermission.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MareSynchronosShared.Models; + +public class UserDefaultPreferredPermission +{ + [Key] + [MaxLength(10)] + [ForeignKey("User")] + public string UserUID { get; set; } + public User User { get; set; } + + public bool DisableIndividualAnimations { get; set; } = false; + public bool DisableIndividualSounds { get; set; } = false; + public bool DisableIndividualVFX { get; set; } = false; + public bool DisableGroupAnimations { get; set; } = false; + public bool DisableGroupSounds { get; set; } = false; + public bool DisableGroupVFX { get; set; } = false; + public bool IndividualIsSticky { get; set; } = false; +} + diff --git a/MareSynchronosServer/MareSynchronosShared/Models/UserPermissionQuery.cs b/MareSynchronosServer/MareSynchronosShared/Models/UserPermissionQuery.cs new file mode 100644 index 0000000..610a7dd --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Models/UserPermissionQuery.cs @@ -0,0 +1,40 @@ +namespace MareSynchronosShared.Models; + +public class UserPermissionQuery +{ + public string UserUID { get; set; } + public string OtherUserUID { get; set; } + public string Alias { get; set; } + public string GID { get; set; } + public bool Synced { get; set; } + public bool OwnpermIsPaused { get; set; } + public bool OwnpermSticky { get; set; } + public bool OwnpermDisableAnimations { get; set; } + public bool OwnpermDisableSounds { get; set; } + public bool OwnpermDisableVFX { get; set; } + public bool? OtherpermIsPaused { get; set; } + public bool? OtherpermDisableAnimations { get; set; } + public bool? OtherpermDisableSounds { get; set; } + public bool? OtherpermDisableVFX { get; set; } + + public UserPermissionSet OwnPermissions => new UserPermissionSet + { + UserUID = UserUID, + OtherUserUID = OtherUserUID, + IsPaused = OwnpermIsPaused, + DisableAnimations = OwnpermDisableAnimations, + DisableSounds = OwnpermDisableSounds, + DisableVFX = OwnpermDisableVFX, + Sticky = OwnpermSticky + }; + + public UserPermissionSet? OtherPermissions => !Synced ? null : new UserPermissionSet + { + UserUID = OtherUserUID, + OtherUserUID = UserUID, + IsPaused = OtherpermIsPaused ?? false, + DisableAnimations = OtherpermDisableAnimations ?? false, + DisableSounds = OtherpermDisableSounds ?? false, + DisableVFX = OtherpermDisableVFX ?? false, + }; +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Models/UserPermissionSet.cs b/MareSynchronosServer/MareSynchronosShared/Models/UserPermissionSet.cs new file mode 100644 index 0000000..34b5f71 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Models/UserPermissionSet.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; + +namespace MareSynchronosShared.Models; + +public class UserPermissionSet +{ + [NotNull] + public string UserUID { get; set; } + public User User { get; set; } + [NotNull] + public string OtherUserUID { get; set; } + public User OtherUser { get; set; } + public bool Sticky { get; set; } = false; + public bool IsPaused { get; set; } = false; + public bool DisableAnimations { get; set; } = false; + public bool DisableVFX { get; set; } = false; + public bool DisableSounds { get; set; } = false; +} diff --git a/MareSynchronosServer/MareSynchronosShared/RequirementHandlers/ValidTokenHubRequirementHandler.cs b/MareSynchronosServer/MareSynchronosShared/RequirementHandlers/ValidTokenHubRequirementHandler.cs new file mode 100644 index 0000000..8d76c9f --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/RequirementHandlers/ValidTokenHubRequirementHandler.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; +using MareSynchronosShared.Utils; +using System.Globalization; + +namespace MareSynchronosShared.RequirementHandlers; + +public class ValidTokenRequirementHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidTokenRequirement requirement) + { + var expirationClaimValue = context.User.Claims.Single(r => string.Equals(r.Type, MareClaimTypes.Expires, StringComparison.Ordinal))?.Value; + if (expirationClaimValue == null) + { + context.Fail(); + } + + DateTime expirationDate = new(long.Parse(expirationClaimValue, CultureInfo.InvariantCulture), DateTimeKind.Utc); + if (expirationDate < DateTime.UtcNow) + { + context.Fail(); + } + + context.Succeed(requirement); + + return Task.CompletedTask; + } +} + +public class ValidTokenHubRequirementHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidTokenRequirement requirement, HubInvocationContext resource) + { + var expirationClaimValue = context.User.Claims.Single(r => string.Equals(r.Type, MareClaimTypes.Expires, StringComparison.Ordinal))?.Value; + if (expirationClaimValue == null) + { + context.Fail(); + } + + DateTime expirationDate = new(long.Parse(expirationClaimValue, CultureInfo.InvariantCulture), DateTimeKind.Utc); + if (expirationDate < DateTime.UtcNow) + { + context.Fail(); + } + + context.Succeed(requirement); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/RequirementHandlers/ValidTokenRequirement.cs b/MareSynchronosServer/MareSynchronosShared/RequirementHandlers/ValidTokenRequirement.cs new file mode 100644 index 0000000..8bcc4b6 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/RequirementHandlers/ValidTokenRequirement.cs @@ -0,0 +1,5 @@ +using Microsoft.AspNetCore.Authorization; + +namespace MareSynchronosShared.RequirementHandlers; + +public class ValidTokenRequirement : IAuthorizationRequirement { } \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Utils/MareClaimTypes.cs b/MareSynchronosServer/MareSynchronosShared/Utils/MareClaimTypes.cs index 08539f4..13f6d1f 100644 --- a/MareSynchronosServer/MareSynchronosShared/Utils/MareClaimTypes.cs +++ b/MareSynchronosServer/MareSynchronosShared/Utils/MareClaimTypes.cs @@ -5,4 +5,5 @@ public static class MareClaimTypes public const string Uid = "uid"; public const string CharaIdent = "character_identification"; public const string Internal = "internal"; + public const string Expires = "expiration_date"; } diff --git a/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs b/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs index a5d84a2..dd941df 100644 --- a/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs +++ b/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs @@ -100,6 +100,16 @@ public static class SharedDbFunctions await dbContext.SaveChangesAsync().ConfigureAwait(false); } + 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); + + dbContext.GroupPairPreferredPermissions.RemoveRange(groupPermissions); + dbContext.UserDefaultPreferredPermissions.RemoveRange(defaultPermissions); + dbContext.Permissions.RemoveRange(individualPermissions); + dbContext.GroupBans.RemoveRange(bannedinGroups); + _logger.LogInformation("User purged: {uid}", user.UID); dbContext.Auth.Remove(auth); diff --git a/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj b/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj index ab914c0..86eadb0 100644 --- a/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj +++ b/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/MareSynchronosServer/MareSynchronosStaticFilesServer/Services/RequestQueueService.cs b/MareSynchronosServer/MareSynchronosStaticFilesServer/Services/RequestQueueService.cs index 8439350..e63f6d4 100644 --- a/MareSynchronosServer/MareSynchronosStaticFilesServer/Services/RequestQueueService.cs +++ b/MareSynchronosServer/MareSynchronosStaticFilesServer/Services/RequestQueueService.cs @@ -201,7 +201,7 @@ public class RequestQueueService : IHostedService _userQueueRequests[i] = null; } - if (!_queue.Any()) return; + if (!_queue.Any() && !_priorityQueue.Any()) return; if (_userQueueRequests[i] == null) { diff --git a/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs b/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs index 9df174c..9afb427 100644 --- a/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs @@ -91,7 +91,7 @@ public class Startup o.TokenValidationParameters = new() { ValidateIssuer = false, - ValidateLifetime = false, + ValidateLifetime = true, ValidateAudience = false, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(s.GetValue(nameof(MareConfigurationAuthBase.Jwt)))),