Refactoring using Claims more, add Server Side Messaging (#20)
* add some refactoring based on claims, handle chara ident inside claim, fix discord userid in log * improve authentication responses, add server side messaging * update server to mainline api Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
namespace MareSynchronosServer.Authentication;
|
||||
|
||||
public record SecretKeyAuthReply(bool Success, string Uid, bool TempBan);
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Collections.Concurrent;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Services;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MareSynchronosServer.Authentication;
|
||||
|
||||
public class SecretKeyAuthenticatorService
|
||||
{
|
||||
private readonly MareMetrics _metrics;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IConfigurationService<MareConfigurationAuthBase> _configurationService;
|
||||
private readonly ILogger<SecretKeyAuthenticatorService> _logger;
|
||||
private readonly ConcurrentDictionary<string, SecretKeyAuthReply> _cachedPositiveResponses = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<string, SecretKeyFailedAuthorization> _failedAuthorizations = new(StringComparer.Ordinal);
|
||||
|
||||
public SecretKeyAuthenticatorService(MareMetrics metrics, IServiceScopeFactory serviceScopeFactory, IConfigurationService<MareConfigurationAuthBase> configuration, ILogger<SecretKeyAuthenticatorService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_configurationService = configuration;
|
||||
_metrics = metrics;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
public async Task<SecretKeyAuthReply> AuthorizeAsync(string ip, string hashedSecretKey)
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationRequests);
|
||||
|
||||
if (_cachedPositiveResponses.TryGetValue(hashedSecretKey, out var cachedPositiveResponse))
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
|
||||
return cachedPositiveResponse;
|
||||
}
|
||||
|
||||
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization)
|
||||
&& existingFailedAuthorization.FailedAttempts > _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.FailedAuthForTempBan), 5))
|
||||
{
|
||||
if (existingFailedAuthorization.ResetTask == null)
|
||||
{
|
||||
_logger.LogWarning("TempBan {ip} for authorization spam", ip);
|
||||
|
||||
existingFailedAuthorization.ResetTask = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(_configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.TempBanDurationInMinutes), 5))).ConfigureAwait(false);
|
||||
|
||||
}).ContinueWith((t) =>
|
||||
{
|
||||
_failedAuthorizations.Remove(ip, out _);
|
||||
});
|
||||
}
|
||||
return new(Success: false, Uid: null, TempBan: true);
|
||||
}
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
using var context = scope.ServiceProvider.GetService<MareDbContext>();
|
||||
var authReply = await context.Auth.AsNoTracking().SingleOrDefaultAsync(u => u.HashedKey == hashedSecretKey).ConfigureAwait(false);
|
||||
|
||||
SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID, false);
|
||||
|
||||
if (reply.Success)
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
|
||||
|
||||
_cachedPositiveResponses[hashedSecretKey] = reply;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||
_cachedPositiveResponses.TryRemove(hashedSecretKey, out _);
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return AuthenticationFailure(ip);
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private SecretKeyAuthReply AuthenticationFailure(string ip)
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||
|
||||
_logger.LogWarning("Failed authorization from {ip}", ip);
|
||||
var whitelisted = _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.WhitelistedIps), new List<string>());
|
||||
if (!whitelisted.Any(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (_failedAuthorizations.TryGetValue(ip, out var auth))
|
||||
{
|
||||
auth.IncreaseFailedAttempts();
|
||||
}
|
||||
else
|
||||
{
|
||||
_failedAuthorizations[ip] = new SecretKeyFailedAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
return new(Success: false, Uid: null, TempBan: false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace MareSynchronosServer.Authentication;
|
||||
|
||||
internal record SecretKeyFailedAuthorization
|
||||
{
|
||||
private int failedAttempts = 1;
|
||||
public int FailedAttempts => failedAttempts;
|
||||
public Task ResetTask { get; set; }
|
||||
public void IncreaseFailedAttempts()
|
||||
{
|
||||
Interlocked.Increment(ref failedAttempts);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Authentication;
|
||||
using MareSynchronosServer.Hubs;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosShared;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Services;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
@@ -17,14 +21,50 @@ namespace MareSynchronosServer.Controllers;
|
||||
public class JwtController : Controller
|
||||
{
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly MareDbContext _mareDbContext;
|
||||
private readonly SecretKeyAuthenticatorService _secretKeyAuthenticatorService;
|
||||
private readonly IConfigurationService<MareConfigurationAuthBase> _configuration;
|
||||
private readonly IClientIdentificationService _clientIdentService;
|
||||
|
||||
public JwtController(IHttpContextAccessor accessor, SecretKeyAuthenticatorService secretKeyAuthenticatorService, IConfigurationService<MareConfigurationAuthBase> configuration)
|
||||
public JwtController(IHttpContextAccessor accessor, MareDbContext mareDbContext,
|
||||
SecretKeyAuthenticatorService secretKeyAuthenticatorService,
|
||||
IConfigurationService<MareConfigurationAuthBase> configuration,
|
||||
IClientIdentificationService clientIdentService)
|
||||
{
|
||||
_accessor = accessor;
|
||||
_mareDbContext = mareDbContext;
|
||||
_secretKeyAuthenticatorService = secretKeyAuthenticatorService;
|
||||
_configuration = configuration;
|
||||
_clientIdentService = clientIdentService;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost(MareAuth.AuthCreateIdent)]
|
||||
public async Task<IActionResult> CreateToken(string auth, string charaIdent)
|
||||
{
|
||||
if (string.IsNullOrEmpty(auth)) return BadRequest("No Authkey");
|
||||
if (string.IsNullOrEmpty(charaIdent)) return BadRequest("No CharaIdent");
|
||||
|
||||
var isBanned = await _mareDbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == charaIdent).ConfigureAwait(false);
|
||||
if (isBanned) return Unauthorized("Your character is banned from using the service.");
|
||||
|
||||
var ip = _accessor.GetIpAddress();
|
||||
|
||||
var authResult = await _secretKeyAuthenticatorService.AuthorizeAsync(ip, auth);
|
||||
|
||||
if (!authResult.Success && !authResult.TempBan) return Unauthorized("The provided secret key is invalid. Verify your accounts existence and/or recover the secret key.");
|
||||
if (!authResult.Success && authResult.TempBan) return Unauthorized("You are temporarily banned. Try connecting again later.");
|
||||
|
||||
var existingIdent = _clientIdentService.GetCharacterIdentForUid(authResult.Uid);
|
||||
if (!string.IsNullOrEmpty(existingIdent)) return Unauthorized("Already logged in to this account.");
|
||||
|
||||
var token = CreateToken(new List<Claim>()
|
||||
{
|
||||
new Claim(MareClaimTypes.Uid, authResult.Uid),
|
||||
new Claim(MareClaimTypes.CharaIdent, charaIdent)
|
||||
});
|
||||
|
||||
return Content(token.RawData);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
@@ -41,7 +81,7 @@ public class JwtController : Controller
|
||||
|
||||
var token = CreateToken(new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, authResult.Uid)
|
||||
new Claim(MareClaimTypes.Uid, authResult.Uid)
|
||||
});
|
||||
|
||||
return Content(token.RawData);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public static class MareClaimTypes
|
||||
{
|
||||
public const string Uid = "uid";
|
||||
public const string CharaIdent = "character_identification";
|
||||
}
|
||||
@@ -58,5 +58,10 @@ namespace MareSynchronosServer.Hubs
|
||||
{
|
||||
throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||
}
|
||||
|
||||
public Task Client_ReceiveServerMessage(MessageSeverity messageSeverity, string message)
|
||||
{
|
||||
throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ public partial class MareHub
|
||||
public async Task FilesAbortUpload()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
var userId = AuthenticatedUserId;
|
||||
var notUploadedFiles = _dbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == userId).ToList();
|
||||
var notUploadedFiles = _dbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == UserUID).ToList();
|
||||
_dbContext.RemoveRange(notUploadedFiles);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
@@ -32,7 +31,7 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var ownFiles = await _dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
var ownFiles = await _dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == UserUID).ToListAsync().ConfigureAwait(false);
|
||||
var request = new DeleteFilesRequest();
|
||||
request.Hash.AddRange(ownFiles.Select(f => f.Hash));
|
||||
Metadata headers = new Metadata()
|
||||
@@ -81,9 +80,8 @@ public partial class MareHub
|
||||
public async Task<bool> FilesIsUploadFinished()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
var userUid = AuthenticatedUserId;
|
||||
return await _dbContext.Files.AsNoTracking()
|
||||
.AnyAsync(f => f.Uploader.UID == userUid && !f.Uploaded).ConfigureAwait(false);
|
||||
.AnyAsync(f => f.Uploader.UID == UserUID && !f.Uploaded).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
@@ -94,7 +92,7 @@ public partial class MareHub
|
||||
var notCoveredFiles = new Dictionary<string, UploadFileDto>(StringComparer.Ordinal);
|
||||
var forbiddenFiles = await _dbContext.ForbiddenUploadEntries.AsNoTracking().Where(f => userSentHashes.Contains(f.Hash)).AsNoTracking().ToDictionaryAsync(f => f.Hash, f => f).ConfigureAwait(false);
|
||||
var existingFiles = await _dbContext.Files.AsNoTracking().Where(f => userSentHashes.Contains(f.Hash)).AsNoTracking().ToDictionaryAsync(f => f.Hash, f => f).ConfigureAwait(false);
|
||||
var uploader = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var uploader = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
|
||||
List<FileCache> fileCachesToUpload = new();
|
||||
foreach (var file in userSentHashes)
|
||||
@@ -117,7 +115,6 @@ public partial class MareHub
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(file, "Missing"));
|
||||
|
||||
var userId = AuthenticatedUserId;
|
||||
fileCachesToUpload.Add(new FileCache()
|
||||
{
|
||||
Hash = file,
|
||||
@@ -143,7 +140,7 @@ public partial class MareHub
|
||||
|
||||
await _uploadSemaphore.WaitAsync(Context.ConnectionAborted).ConfigureAwait(false);
|
||||
|
||||
var relatedFile = _dbContext.Files.SingleOrDefault(f => f.Hash == hash && f.Uploader.UID == AuthenticatedUserId && !f.Uploaded);
|
||||
var relatedFile = _dbContext.Files.SingleOrDefault(f => f.Hash == hash && f.Uploader.UID == UserUID && !f.Uploaded);
|
||||
if (relatedFile == null)
|
||||
{
|
||||
_uploadSemaphore.Release();
|
||||
@@ -226,7 +223,7 @@ public partial class MareHub
|
||||
{
|
||||
FileData = ByteString.CopyFrom(data, 0, readBytes),
|
||||
Hash = computedHashString,
|
||||
Uploader = AuthenticatedUserId
|
||||
Uploader = UserUID
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
await streamingCall.RequestStream.CompleteAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using MareSynchronosShared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MareSynchronosServer.Utils;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
@@ -9,7 +8,7 @@ public partial class MareHub
|
||||
{
|
||||
private async Task<List<PausedEntry>> GetAllPairedClientsWithPauseState(string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
uid ??= UserUID;
|
||||
|
||||
var query = await (from userPair in _dbContext.ClientPairs
|
||||
join otherUserPair in _dbContext.ClientPairs on userPair.OtherUserUID equals otherUserPair.UserUID
|
||||
@@ -47,7 +46,7 @@ public partial class MareHub
|
||||
|
||||
private async Task<List<string>> GetAllPairedUnpausedUsers(string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
uid ??= UserUID;
|
||||
var ret = await GetAllPairedClientsWithPauseState(uid).ConfigureAwait(false);
|
||||
return ret.Where(k => !k.IsPaused).Select(k => k.UID).ToList();
|
||||
}
|
||||
@@ -68,11 +67,12 @@ public partial class MareHub
|
||||
return usersToSendDataTo;
|
||||
}
|
||||
|
||||
public string AuthenticatedUserId => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||
public string UserUID => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value ?? throw new Exception("No UID in Claims");
|
||||
public string UserCharaIdent => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))?.Value ?? throw new Exception("No Chara Ident in Claims");
|
||||
|
||||
private async Task UserGroupLeave(GroupPair groupUserPair, List<PausedEntry> allUserPairs, string userIdent, string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
uid ??= UserUID;
|
||||
var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
if (userPair != null)
|
||||
{
|
||||
@@ -106,7 +106,7 @@ public partial class MareHub
|
||||
|
||||
private async Task<(bool IsValid, GroupPair ReferredPair)> TryValidateUserInGroup(string gid, string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
uid ??= UserUID;
|
||||
|
||||
var groupPair = await _dbContext.GroupPairs.Include(c => c.GroupUser)
|
||||
.SingleOrDefaultAsync(g => g.GroupGID == gid && (g.GroupUserUID == uid || g.GroupUser.Alias == uid)).ConfigureAwait(false);
|
||||
@@ -122,7 +122,7 @@ public partial class MareHub
|
||||
|
||||
if (isOwnerResult.ReferredGroup == null) return (false, null);
|
||||
|
||||
var groupPairSelf = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var groupPairSelf = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
if (groupPairSelf == null || !groupPairSelf.IsModerator) return (false, null);
|
||||
|
||||
return (true, isOwnerResult.ReferredGroup);
|
||||
@@ -133,6 +133,6 @@ public partial class MareHub
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null) return (false, null);
|
||||
|
||||
return (string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal), group);
|
||||
return (string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal), group);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ public partial class MareHub
|
||||
public async Task<GroupCreatedDto> GroupCreate()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
var existingGroupsByUser = await _dbContext.Groups.CountAsync(u => u.OwnerUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var existingJoinedGroups = await _dbContext.GroupPairs.CountAsync(u => u.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var existingGroupsByUser = await _dbContext.Groups.CountAsync(u => u.OwnerUID == UserUID).ConfigureAwait(false);
|
||||
var existingJoinedGroups = await _dbContext.GroupPairs.CountAsync(u => u.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
if (existingGroupsByUser >= _maxExistingGroupsByUser || existingJoinedGroups >= _maxJoinedGroupsByUser)
|
||||
{
|
||||
throw new System.Exception($"Max groups for user is {_maxExistingGroupsByUser}, max joined groups is {_maxJoinedGroupsByUser}.");
|
||||
@@ -38,13 +38,13 @@ public partial class MareHub
|
||||
GID = gid,
|
||||
HashedPassword = hashedPw,
|
||||
InvitesEnabled = true,
|
||||
OwnerUID = AuthenticatedUserId
|
||||
OwnerUID = UserUID
|
||||
};
|
||||
|
||||
GroupPair initialPair = new()
|
||||
{
|
||||
GroupGID = newGroup.GID,
|
||||
GroupUserUID = AuthenticatedUserId,
|
||||
GroupUserUID = UserUID,
|
||||
IsPaused = false,
|
||||
IsPinned = true
|
||||
};
|
||||
@@ -53,9 +53,9 @@ public partial class MareHub
|
||||
await _dbContext.GroupPairs.AddAsync(initialPair).ConfigureAwait(false);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
var self = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var self = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).Client_GroupChange(new GroupDto()
|
||||
await Clients.User(UserUID).Client_GroupChange(new GroupDto()
|
||||
{
|
||||
GID = newGroup.GID,
|
||||
OwnedBy = string.IsNullOrEmpty(self.Alias) ? self.UID : self.Alias,
|
||||
@@ -78,7 +78,7 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var groups = await _dbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == AuthenticatedUserId).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
var groups = await _dbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
|
||||
return groups.Select(g => new GroupDto()
|
||||
{
|
||||
@@ -99,7 +99,7 @@ public partial class MareHub
|
||||
var (inGroup, _) = await TryValidateUserInGroup(gid).ConfigureAwait(false);
|
||||
if (!inGroup) return new List<GroupPairDto>();
|
||||
|
||||
var allPairs = await _dbContext.GroupPairs.Include(g => g.GroupUser).Where(g => g.GroupGID == gid && g.GroupUserUID != AuthenticatedUserId).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
var allPairs = await _dbContext.GroupPairs.Include(g => g.GroupUser).Where(g => g.GroupGID == gid && g.GroupUserUID != UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
return allPairs.Select(p => new GroupPairDto()
|
||||
{
|
||||
GroupGID = gid,
|
||||
@@ -160,14 +160,16 @@ public partial class MareHub
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<bool> GroupJoin(string gid, string password)
|
||||
{
|
||||
gid = gid.Trim();
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(gid));
|
||||
|
||||
var group = await _dbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == gid || g.Alias == gid).ConfigureAwait(false);
|
||||
var existingPair = await _dbContext.GroupPairs.AsNoTracking().SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var existingPair = await _dbContext.GroupPairs.AsNoTracking().SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
var hashedPw = StringUtils.Sha256String(password);
|
||||
var existingUserCount = await _dbContext.GroupPairs.AsNoTracking().CountAsync(g => g.GroupGID == gid).ConfigureAwait(false);
|
||||
var joinedGroups = await _dbContext.GroupPairs.CountAsync(g => g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var isBanned = await _dbContext.GroupBans.AnyAsync(g => g.GroupGID == gid && g.BannedUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var joinedGroups = await _dbContext.GroupPairs.CountAsync(g => g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
var isBanned = await _dbContext.GroupBans.AnyAsync(g => g.GroupGID == gid && g.BannedUserUID == UserUID).ConfigureAwait(false);
|
||||
var groupGid = group?.GID ?? string.Empty;
|
||||
var oneTimeInvite = await _dbContext.GroupTempInvites.SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.Invite == hashedPw).ConfigureAwait(false);
|
||||
|
||||
@@ -189,7 +191,7 @@ public partial class MareHub
|
||||
GroupPair newPair = new()
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
GroupUserUID = AuthenticatedUserId
|
||||
GroupUserUID = UserUID
|
||||
};
|
||||
|
||||
await _dbContext.GroupPairs.AddAsync(newPair).ConfigureAwait(false);
|
||||
@@ -197,7 +199,7 @@ public partial class MareHub
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(gid, "Success"));
|
||||
|
||||
await Clients.User(AuthenticatedUserId).Client_GroupChange(new GroupDto()
|
||||
await Clients.User(UserUID).Client_GroupChange(new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
OwnedBy = string.IsNullOrEmpty(group.Owner.Alias) ? group.Owner.UID : group.Owner.Alias,
|
||||
@@ -207,15 +209,15 @@ public partial class MareHub
|
||||
InvitesEnabled = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var self = _dbContext.Users.Single(u => u.UID == AuthenticatedUserId);
|
||||
var self = _dbContext.Users.Single(u => u.UID == UserUID);
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID && p.GroupUserUID != AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID && p.GroupUserUID != UserUID).ToListAsync().ConfigureAwait(false);
|
||||
await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).Client_GroupUserChange(new GroupPairDto()
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
IsPaused = false,
|
||||
IsRemoved = false,
|
||||
UserUID = AuthenticatedUserId,
|
||||
UserUID = UserUID,
|
||||
UserAlias = self.Alias,
|
||||
IsPinned = false,
|
||||
IsModerator = false,
|
||||
@@ -223,7 +225,6 @@ public partial class MareHub
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
var userIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
foreach (var groupUserPair in groupPairs)
|
||||
{
|
||||
var userPair = allUserPairs.Single(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
@@ -234,8 +235,8 @@ public partial class MareHub
|
||||
var groupUserIdent = _clientIdentService.GetCharacterIdentForUid(groupUserPair.GroupUserUID);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Clients.User(AuthenticatedUserId).Client_UserChangePairedPlayer(groupUserIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).Client_UserChangePairedPlayer(userIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(UserUID).Client_UserChangePairedPlayer(groupUserIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).Client_UserChangePairedPlayer(UserCharaIdent, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,18 +293,18 @@ public partial class MareHub
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID).ToListAsync().ConfigureAwait(false);
|
||||
var groupPairsWithoutSelf = groupPairs.Where(p => !string.Equals(p.GroupUserUID, AuthenticatedUserId, StringComparison.Ordinal)).ToList();
|
||||
var groupPairsWithoutSelf = groupPairs.Where(p => !string.Equals(p.GroupUserUID, UserUID, StringComparison.Ordinal)).ToList();
|
||||
|
||||
_dbContext.GroupPairs.Remove(groupPair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).Client_GroupChange(new GroupDto()
|
||||
await Clients.User(UserUID).Client_GroupChange(new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
IsDeleted = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
bool ownerHasLeft = string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal);
|
||||
bool ownerHasLeft = string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal);
|
||||
if (ownerHasLeft)
|
||||
{
|
||||
if (!groupPairsWithoutSelf.Any())
|
||||
@@ -352,15 +353,14 @@ public partial class MareHub
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
IsRemoved = true,
|
||||
UserUID = AuthenticatedUserId,
|
||||
UserUID = UserUID,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
var userIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
foreach (var groupUserPair in groupPairsWithoutSelf)
|
||||
{
|
||||
await UserGroupLeave(groupUserPair, allUserPairs, userIdent).ConfigureAwait(false);
|
||||
await UserGroupLeave(groupUserPair, allUserPairs, UserCharaIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,15 +377,15 @@ public partial class MareHub
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(gid, isPaused, "Success"));
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid && p.GroupUserUID != AuthenticatedUserId).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid && p.GroupUserUID != UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).Client_GroupUserChange(new GroupPairDto()
|
||||
{
|
||||
GroupGID = gid,
|
||||
IsPaused = isPaused,
|
||||
UserUID = AuthenticatedUserId,
|
||||
UserUID = UserUID,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).Client_GroupChange(new GroupDto
|
||||
await Clients.User(UserUID).Client_GroupChange(new GroupDto
|
||||
{
|
||||
GID = gid,
|
||||
IsPaused = isPaused
|
||||
@@ -393,7 +393,6 @@ public partial class MareHub
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
var userIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
foreach (var groupUserPair in groupPairs)
|
||||
{
|
||||
var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
@@ -407,8 +406,8 @@ public partial class MareHub
|
||||
var groupUserIdent = _clientIdentService.GetCharacterIdentForUid(groupUserPair.GroupUserUID);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Clients.User(AuthenticatedUserId).Client_UserChangePairedPlayer(groupUserIdent, !isPaused).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).Client_UserChangePairedPlayer(userIdent, !isPaused).ConfigureAwait(false);
|
||||
await Clients.User(UserUID).Client_UserChangePairedPlayer(groupUserIdent, !isPaused).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).Client_UserChangePairedPlayer(UserCharaIdent, !isPaused).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,7 +470,7 @@ public partial class MareHub
|
||||
var alias = string.IsNullOrEmpty(groupPair.GroupUser.Alias) ? "-" : groupPair.GroupUser.Alias;
|
||||
var ban = new GroupBan()
|
||||
{
|
||||
BannedByUID = AuthenticatedUserId,
|
||||
BannedByUID = UserUID,
|
||||
BannedReason = $"{reason} (Alias at time of ban: {alias})",
|
||||
BannedOn = DateTime.UtcNow,
|
||||
BannedUserUID = uid,
|
||||
@@ -574,7 +573,7 @@ public partial class MareHub
|
||||
var ownedShells = await _dbContext.Groups.CountAsync(g => g.OwnerUID == uid).ConfigureAwait(false);
|
||||
if (ownedShells >= _maxExistingGroupsByUser) return;
|
||||
|
||||
var prevOwner = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var prevOwner = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
prevOwner.IsPinned = false;
|
||||
group.Owner = newOwnerPair.GroupUser;
|
||||
group.Alias = null;
|
||||
|
||||
@@ -16,13 +16,11 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
string userid = AuthenticatedUserId;
|
||||
var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == userid).ConfigureAwait(false);
|
||||
var charaIdent = _clientIdentService.GetCharacterIdentForUid(userid);
|
||||
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == userid).ToListAsync().ConfigureAwait(false);
|
||||
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == userid).ConfigureAwait(false);
|
||||
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == userid).ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == userid).ToListAsync().ConfigureAwait(false);
|
||||
var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == UserUID).ToListAsync().ConfigureAwait(false);
|
||||
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == UserUID).ConfigureAwait(false);
|
||||
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == UserUID).ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == UserUID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
if (lodestone != null)
|
||||
{
|
||||
@@ -37,12 +35,12 @@ public partial class MareHub
|
||||
_dbContext.ClientPairs.RemoveRange(ownPairData);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
var otherPairData = await _dbContext.ClientPairs.Include(u => u.User)
|
||||
.Where(u => u.OtherUser.UID == userid).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
.Where(u => u.OtherUser.UID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
foreach (var pair in otherPairData)
|
||||
{
|
||||
await Clients.User(pair.User.UID).Client_UserUpdateClientPairs(new ClientPairDto()
|
||||
{
|
||||
OtherUID = userid,
|
||||
OtherUID = UserUID,
|
||||
IsRemoved = true
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
@@ -65,9 +63,7 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
|
||||
var usersToSendOnlineTo = await SendOnlineToAllPairedUsers(ownIdent).ConfigureAwait(false);
|
||||
var usersToSendOnlineTo = await SendOnlineToAllPairedUsers(UserCharaIdent).ConfigureAwait(false);
|
||||
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToList();
|
||||
}
|
||||
|
||||
@@ -76,7 +72,6 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
string userid = AuthenticatedUserId;
|
||||
var query =
|
||||
from userToOther in _dbContext.ClientPairs
|
||||
join otherToUser in _dbContext.ClientPairs
|
||||
@@ -92,7 +87,7 @@ public partial class MareHub
|
||||
} into leftJoin
|
||||
from otherEntry in leftJoin.DefaultIfEmpty()
|
||||
where
|
||||
userToOther.UserUID == userid
|
||||
userToOther.UserUID == UserUID
|
||||
select new
|
||||
{
|
||||
userToOther.OtherUser.Alias,
|
||||
@@ -140,22 +135,24 @@ public partial class MareHub
|
||||
}
|
||||
}
|
||||
|
||||
if (hadInvalidData) throw new HubException("Invalid data provided, contact the appropriate mod creator to resolve those issues"
|
||||
+ Environment.NewLine
|
||||
if (hadInvalidData)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "One or more of your supplied mods were rejected from the server. Consult /xllog for more information.").ConfigureAwait(false);
|
||||
throw new HubException("Invalid data provided, contact the appropriate mod creator to resolve those issues"
|
||||
+ Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, invalidGamePaths.Select(p => "Invalid Game Path: " + p))
|
||||
+ Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, invalidFileSwapPaths.Select(p => "Invalid FileSwap Path: " + p)));
|
||||
}
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
|
||||
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), StringComparer.Ordinal)
|
||||
.Where(f => visibleCharacterIds.Contains(f.Value, StringComparer.Ordinal));
|
||||
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(visibleCharacterIds.Count, allPairedUsersDict.Count()));
|
||||
|
||||
await Clients.Users(allPairedUsersDict.Select(f => f.Key)).Client_UserReceiveCharacterData(characterCache, ownIdent).ConfigureAwait(false);
|
||||
await Clients.Users(allPairedUsersDict.Select(f => f.Key)).Client_UserReceiveCharacterData(characterCache, UserCharaIdent).ConfigureAwait(false);
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushData);
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, allPairedUsersDict.Count());
|
||||
@@ -168,18 +165,22 @@ public partial class MareHub
|
||||
|
||||
// don't allow adding yourself or nothing
|
||||
uid = uid.Trim();
|
||||
if (string.Equals(uid, AuthenticatedUserId, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
||||
if (string.Equals(uid, UserUID, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
||||
|
||||
// grab other user, check if it exists and if a pair already exists
|
||||
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
|
||||
var existingEntry =
|
||||
await _dbContext.ClientPairs.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p =>
|
||||
p.User.UID == AuthenticatedUserId && p.OtherUserUID == uid).ConfigureAwait(false);
|
||||
if (otherUser == null || existingEntry != null) return;
|
||||
p.User.UID == UserUID && p.OtherUserUID == uid).ConfigureAwait(false);
|
||||
if (otherUser == null || existingEntry != null)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, $"Cannot pair with {uid}, either already paired or UID does not exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// grab self create new client pair and save
|
||||
var user = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var user = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(uid, "Success"));
|
||||
|
||||
@@ -239,8 +240,8 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid, isPaused));
|
||||
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
if (string.Equals(otherUserUid, UserUID, StringComparison.Ordinal)) return;
|
||||
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
if (pair == null) return;
|
||||
|
||||
pair.IsPaused = isPaused;
|
||||
@@ -251,7 +252,7 @@ public partial class MareHub
|
||||
|
||||
var otherEntry = OppositeEntry(otherUserUid);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).Client_UserUpdateClientPairs(
|
||||
await Clients.User(UserUID).Client_UserUpdateClientPairs(
|
||||
new ClientPairDto()
|
||||
{
|
||||
OtherUID = otherUserUid,
|
||||
@@ -263,19 +264,18 @@ public partial class MareHub
|
||||
{
|
||||
await Clients.User(otherUserUid).Client_UserUpdateClientPairs(new ClientPairDto()
|
||||
{
|
||||
OtherUID = AuthenticatedUserId,
|
||||
OtherUID = UserUID,
|
||||
IsPaused = otherEntry.IsPaused,
|
||||
IsPausedFromOthers = isPaused,
|
||||
IsSynced = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var selfCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
var otherCharaIdent = _clientIdentService.GetCharacterIdentForUid(pair.OtherUserUID);
|
||||
|
||||
if (selfCharaIdent == null || otherCharaIdent == null || otherEntry.IsPaused) return;
|
||||
if (UserCharaIdent == null || otherCharaIdent == null || otherEntry.IsPaused) return;
|
||||
|
||||
await Clients.User(AuthenticatedUserId).Client_UserChangePairedPlayer(otherCharaIdent, !isPaused).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).Client_UserChangePairedPlayer(selfCharaIdent, !isPaused).ConfigureAwait(false);
|
||||
await Clients.User(UserUID).Client_UserChangePairedPlayer(otherCharaIdent, !isPaused).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).Client_UserChangePairedPlayer(UserCharaIdent, !isPaused).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,11 +284,11 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid));
|
||||
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
if (string.Equals(otherUserUid, UserUID, StringComparison.Ordinal)) return;
|
||||
|
||||
// check if client pair even exists
|
||||
ClientPair callerPair =
|
||||
await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
bool callerHadPaused = callerPair.IsPaused;
|
||||
if (callerPair == null) return;
|
||||
|
||||
@@ -298,7 +298,7 @@ public partial class MareHub
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid, "Success"));
|
||||
|
||||
await Clients.User(AuthenticatedUserId)
|
||||
await Clients.User(UserUID)
|
||||
.Client_UserUpdateClientPairs(new ClientPairDto()
|
||||
{
|
||||
OtherUID = otherUserUid,
|
||||
@@ -317,7 +317,7 @@ public partial class MareHub
|
||||
await Clients.User(otherUserUid).Client_UserUpdateClientPairs(
|
||||
new ClientPairDto()
|
||||
{
|
||||
OtherUID = AuthenticatedUserId,
|
||||
OtherUID = UserUID,
|
||||
IsPausedFromOthers = false,
|
||||
IsSynced = false
|
||||
}).ConfigureAwait(false);
|
||||
@@ -336,20 +336,18 @@ public partial class MareHub
|
||||
// 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)
|
||||
{
|
||||
var userIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
await Clients.User(AuthenticatedUserId).Client_UserChangePairedPlayer(otherIdent, false).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).Client_UserChangePairedPlayer(userIdent, false).ConfigureAwait(false);
|
||||
await Clients.User(UserUID).Client_UserChangePairedPlayer(otherIdent, false).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).Client_UserChangePairedPlayer(UserCharaIdent, false).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)
|
||||
{
|
||||
var userIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
await Clients.User(AuthenticatedUserId).Client_UserChangePairedPlayer(otherIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).Client_UserChangePairedPlayer(userIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(UserUID).Client_UserChangePairedPlayer(otherIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).Client_UserChangePairedPlayer(UserCharaIdent, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientPair OppositeEntry(string otherUID) =>
|
||||
_dbContext.ClientPairs.AsNoTracking().SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == AuthenticatedUserId);
|
||||
_dbContext.ClientPairs.AsNoTracking().SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == UserUID);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared;
|
||||
@@ -10,7 +9,6 @@ using MareSynchronosShared.Services;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
@@ -51,57 +49,40 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
_dbContext = mareDbContext;
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Authenticated")]
|
||||
public async Task<ConnectionDto> Heartbeat(string characterIdentification)
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<ConnectionDto> GetConnectionDto()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterInitializedConnections);
|
||||
|
||||
var userId = Context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value;
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(characterIdentification));
|
||||
|
||||
await Clients.Caller.Client_UpdateSystemInfo(_systemInfoService.SystemInfoDto).ConfigureAwait(false);
|
||||
|
||||
var isBanned = await _dbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == characterIdentification).ConfigureAwait(false);
|
||||
var dbUser = _dbContext.Users.SingleOrDefault(f => f.UID == UserUID);
|
||||
dbUser.LastLoggedIn = DateTime.UtcNow;
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(userId) && !isBanned && !string.IsNullOrEmpty(characterIdentification))
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, "Welcome to Mare Synchronos \"" + _shardName + "\", Current Online Users: " + _systemInfoService.SystemInfoDto.OnlineUsers).ConfigureAwait(false);
|
||||
|
||||
return new ConnectionDto()
|
||||
{
|
||||
var user = (await _dbContext.Users.SingleAsync(u => u.UID == userId).ConfigureAwait(false));
|
||||
var existingIdent = _clientIdentService.GetCharacterIdentForUid(userId);
|
||||
if (!string.IsNullOrEmpty(existingIdent) && !string.Equals(characterIdentification, existingIdent, StringComparison.Ordinal))
|
||||
ServerVersion = IMareHub.ApiVersion,
|
||||
UID = string.IsNullOrEmpty(dbUser.Alias) ? dbUser.UID : dbUser.Alias,
|
||||
IsAdmin = dbUser.IsAdmin,
|
||||
IsModerator = dbUser.IsModerator,
|
||||
ServerInfo = new ServerInfoDto()
|
||||
{
|
||||
_logger.LogCallWarning(MareHubLogger.Args(characterIdentification, "Failure", "LoggedIn"));
|
||||
|
||||
return new ConnectionDto()
|
||||
{
|
||||
ServerVersion = IMareHub.ApiVersion
|
||||
};
|
||||
MaxGroupsCreatedByUser = _maxExistingGroupsByUser,
|
||||
ShardName = _shardName,
|
||||
MaxGroupsJoinedByUser = _maxJoinedGroupsByUser,
|
||||
MaxGroupUserCount = _maxGroupUserCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
user.LastLoggedIn = DateTime.UtcNow;
|
||||
_clientIdentService.MarkUserOnline(user.UID, characterIdentification);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(characterIdentification, "Success"));
|
||||
|
||||
return new ConnectionDto
|
||||
{
|
||||
ServerVersion = IMareHub.ApiVersion,
|
||||
UID = string.IsNullOrEmpty(user.Alias) ? user.UID : user.Alias,
|
||||
IsModerator = user.IsModerator,
|
||||
IsAdmin = user.IsAdmin,
|
||||
ServerInfo = new ServerInfoDto()
|
||||
{
|
||||
MaxGroupsCreatedByUser = _maxExistingGroupsByUser,
|
||||
ShardName = _shardName,
|
||||
MaxGroupsJoinedByUser = _maxJoinedGroupsByUser,
|
||||
MaxGroupUserCount = _maxGroupUserCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_logger.LogCallWarning(MareHubLogger.Args(characterIdentification, "Failure"));
|
||||
|
||||
[Authorize(Policy = "Authenticated")]
|
||||
public async Task<ConnectionDto> Heartbeat(string characterIdentification)
|
||||
{
|
||||
return new ConnectionDto()
|
||||
{
|
||||
ServerVersion = IMareHub.ApiVersion
|
||||
@@ -109,21 +90,31 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Authenticated")]
|
||||
public Task<bool> CheckClientHealth()
|
||||
public async Task<bool> CheckClientHealth()
|
||||
{
|
||||
var needsReconnect = !_clientIdentService.IsOnCurrentServer(AuthenticatedUserId);
|
||||
var needsReconnect = !_clientIdentService.IsOnCurrentServer(UserUID);
|
||||
if (needsReconnect)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "Internal server state corruption detected, reconnecting you automatically to fix the issue.").ConfigureAwait(false);
|
||||
_logger.LogCallWarning(MareHubLogger.Args(needsReconnect));
|
||||
}
|
||||
return Task.FromResult(needsReconnect);
|
||||
|
||||
return needsReconnect;
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Authenticated")]
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(_contextAccessor.GetIpAddress()));
|
||||
_mareMetrics.IncGauge(MetricsAPI.GaugeConnections);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(_contextAccessor.GetIpAddress(), UserCharaIdent));
|
||||
|
||||
_clientIdentService.MarkUserOnline(UserUID, UserCharaIdent);
|
||||
}
|
||||
catch { }
|
||||
|
||||
await base.OnConnectedAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -132,19 +123,18 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
{
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugeConnections);
|
||||
|
||||
var userCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
|
||||
if (!string.IsNullOrEmpty(userCharaIdent))
|
||||
try
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
_clientIdentService.MarkUserOffline(AuthenticatedUserId);
|
||||
_logger.LogCallInfo(MareHubLogger.Args(_contextAccessor.GetIpAddress(), UserCharaIdent));
|
||||
|
||||
await SendOfflineToAllPairedUsers(userCharaIdent).ConfigureAwait(false);
|
||||
_clientIdentService.MarkUserOffline(UserUID);
|
||||
|
||||
_dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == AuthenticatedUserId));
|
||||
await SendOfflineToAllPairedUsers(UserCharaIdent).ConfigureAwait(false);
|
||||
|
||||
_dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == UserUID));
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
|
||||
await base.OnDisconnectedAsync(exception).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using AspNetCoreRateLimit;
|
||||
using AspNetCoreRateLimit;
|
||||
using MareSynchronosShared;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -37,7 +36,7 @@ public class SignalRLimitFilter : IHubFilter
|
||||
var counter = await _processor.ProcessRequestAsync(client, rule).ConfigureAwait(false);
|
||||
if (counter.Count > rule.Limit)
|
||||
{
|
||||
var authUserId = invocationContext.Context.User.Claims?.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||
var authUserId = invocationContext.Context.User.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||
var retry = counter.Timestamp.RetryAfterFrom(rule);
|
||||
logger.LogWarning("Method rate limit triggered from {ip}/{authUserId}: {method}", ip, authUserId, invocationContext.HubMethodName);
|
||||
throw new HubException($"call limit {retry}");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using MareSynchronosShared.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosServer.Hubs;
|
||||
|
||||
namespace MareSynchronosServer.RequirementHandlers;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class UserRequirementHandler : AuthorizationHandler<UserRequirement, HubI
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, UserRequirement requirement, HubInvocationContext resource)
|
||||
{
|
||||
var uid = context.User.Claims.SingleOrDefault(g => string.Equals(g.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value;
|
||||
var uid = context.User.Claims.SingleOrDefault(g => string.Equals(g.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value;
|
||||
|
||||
if (uid == null) context.Fail();
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Grpc.Core;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Hubs;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using static MareSynchronosShared.Protos.ClientMessageService;
|
||||
|
||||
namespace MareSynchronosServer.Services;
|
||||
|
||||
public class GrpcClientMessageService : ClientMessageServiceBase
|
||||
{
|
||||
private readonly ILogger<GrpcClientMessageService> _logger;
|
||||
private readonly IHubContext<MareHub, IMareHub> _hubContext;
|
||||
|
||||
public GrpcClientMessageService(ILogger<GrpcClientMessageService> logger, IHubContext<MareHub, IMareHub> hubContext)
|
||||
{
|
||||
_logger = logger;
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
public override async Task<Empty> SendClientMessage(ClientMessage request, ServerCallContext context)
|
||||
{
|
||||
bool hasUid = !string.IsNullOrEmpty(request.Uid);
|
||||
|
||||
var severity = request.Type switch
|
||||
{
|
||||
MessageType.Info => MessageSeverity.Information,
|
||||
MessageType.Warning => MessageSeverity.Warning,
|
||||
MessageType.Error => MessageSeverity.Error,
|
||||
_ => MessageSeverity.Information,
|
||||
};
|
||||
|
||||
if (!hasUid)
|
||||
{
|
||||
_logger.LogInformation("Sending Message of severity {severity} to all online users: {message}", severity, request.Message);
|
||||
await _hubContext.Clients.All.Client_ReceiveServerMessage(severity, request.Message).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Sending Message of severity {severity} to user {uid}: {message}", severity, request.Uid, request.Message);
|
||||
await _hubContext.Clients.User(request.Uid).Client_ReceiveServerMessage(severity, request.Message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
}
|
||||
@@ -94,9 +94,10 @@ public class GrpcClientIdentificationService : GrpcBaseService, IClientIdentific
|
||||
|
||||
public void MarkUserOffline(string uid)
|
||||
{
|
||||
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
|
||||
if (OnlineClients.TryRemove(uid, out var uidWithIdent))
|
||||
{
|
||||
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
_identChangeQueue.Enqueue(new IdentChange()
|
||||
{
|
||||
IsOnline = false,
|
||||
@@ -172,6 +173,7 @@ public class GrpcClientIdentificationService : GrpcBaseService, IClientIdentific
|
||||
_logger.LogInformation("Starting Receive Online Client Data stream");
|
||||
await foreach (var cur in stream.ResponseStream.ReadAllAsync(cts).ConfigureAwait(false))
|
||||
{
|
||||
OnlineClients.Remove(cur.UidWithIdent.Uid.Uid, out _);
|
||||
if (cur.IsOnline)
|
||||
{
|
||||
RemoteCachedIdents[cur.UidWithIdent.Uid.Uid] = cur.UidWithIdent;
|
||||
|
||||
@@ -47,14 +47,14 @@ public class SystemInfoService : IHostedService, IDisposable
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableWorkerThreads, workerThreads);
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableIOWorkerThreads, ioThreads);
|
||||
|
||||
var onlineUsers = (int)_clientIdentService.GetOnlineUsers().Result;
|
||||
SystemInfoDto = new SystemInfoDto()
|
||||
{
|
||||
OnlineUsers = onlineUsers,
|
||||
};
|
||||
|
||||
if (_config.IsMain)
|
||||
{
|
||||
var onlineUsers = (int)_clientIdentService.GetOnlineUsers().Result;
|
||||
SystemInfoDto = new SystemInfoDto()
|
||||
{
|
||||
OnlineUsers = onlineUsers,
|
||||
};
|
||||
|
||||
_logger.LogInformation("Sending System Info, Online Users: {onlineUsers}", onlineUsers);
|
||||
|
||||
_hubContext.Clients.All.Client_UpdateSystemInfo(SystemInfoDto);
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using AspNetCoreRateLimit;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Grpc.Net.Client.Configuration;
|
||||
@@ -22,6 +21,7 @@ using Grpc.Net.ClientFactory;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using MareSynchronosServer.Authentication;
|
||||
|
||||
namespace MareSynchronosServer;
|
||||
|
||||
@@ -79,7 +79,7 @@ public class Startup
|
||||
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
||||
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
||||
// configure services based on main server status
|
||||
ConfigureIdentityServices(services, mareConfig, isMainServer);
|
||||
ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer);
|
||||
|
||||
if (isMainServer)
|
||||
{
|
||||
@@ -210,7 +210,7 @@ public class Startup
|
||||
}));
|
||||
}
|
||||
|
||||
private static void ConfigureIdentityServices(IServiceCollection services, IConfigurationSection mareConfig, bool isMainServer)
|
||||
private static void ConfigureServicesBasedOnShardType(IServiceCollection services, IConfigurationSection mareConfig, bool isMainServer)
|
||||
{
|
||||
if (!isMainServer)
|
||||
{
|
||||
@@ -325,6 +325,7 @@ public class Startup
|
||||
{
|
||||
endpoints.MapGrpcService<GrpcIdentityService>().AllowAnonymous();
|
||||
endpoints.MapGrpcService<GrpcConfigurationService<ServerConfiguration>>().AllowAnonymous();
|
||||
endpoints.MapGrpcService<GrpcClientMessageService>().AllowAnonymous();
|
||||
}
|
||||
|
||||
endpoints.MapHealthChecks("/health").AllowAnonymous();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using MareSynchronosServer.Hubs;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace MareSynchronosServer.Utils;
|
||||
@@ -7,6 +7,6 @@ public class IdBasedUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string GetUserId(HubConnectionContext context)
|
||||
{
|
||||
return context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value;
|
||||
return context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ public class MareHubLogger
|
||||
public void LogCallInfo(object[] args = null, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
string formattedArgs = args != null && args.Length != 0 ? "|" + string.Join(":", args) : string.Empty;
|
||||
_logger.LogInformation("{uid}:{method}{args}", _hub.AuthenticatedUserId, methodName, formattedArgs);
|
||||
_logger.LogInformation("{uid}:{method}{args}", _hub.UserUID, methodName, formattedArgs);
|
||||
}
|
||||
|
||||
public void LogCallWarning(object[] args = null, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
string formattedArgs = args != null && args.Length != 0 ? "|" + string.Join(":", args) : string.Empty;
|
||||
_logger.LogWarning("{uid}:{method}{args}", _hub.AuthenticatedUserId, methodName, formattedArgs);
|
||||
_logger.LogWarning("{uid}:{method}{args}", _hub.UserUID, methodName, formattedArgs);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user