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:
rootdarkarchon
2023-01-04 15:49:18 +01:00
committed by GitHub
parent 5f0c12ecfa
commit 74b7fcdf89
25 changed files with 350 additions and 204 deletions

View File

@@ -0,0 +1,3 @@
namespace MareSynchronosServer.Authentication;
public record SecretKeyAuthReply(bool Success, string Uid, bool TempBan);

View File

@@ -4,10 +4,8 @@ using MareSynchronosShared.Metrics;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MareSynchronosShared.Authentication;
namespace MareSynchronosServer.Authentication;
public class SecretKeyAuthenticatorService
{
@@ -16,7 +14,7 @@ public class SecretKeyAuthenticatorService
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);
private readonly ConcurrentDictionary<string, SecretKeyFailedAuthorization> _failedAuthorizations = new(StringComparer.Ordinal);
public SecretKeyAuthenticatorService(MareMetrics metrics, IServiceScopeFactory serviceScopeFactory, IConfigurationService<MareConfigurationAuthBase> configuration, ILogger<SecretKeyAuthenticatorService> logger)
{
@@ -52,14 +50,14 @@ public class SecretKeyAuthenticatorService
_failedAuthorizations.Remove(ip, out _);
});
}
return new(Success: false, Uid: null);
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);
SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID, false);
if (reply.Success)
{
@@ -99,6 +97,6 @@ public class SecretKeyAuthenticatorService
}
}
return new(Success: false, Uid: null);
return new(Success: false, Uid: null, TempBan: false);
}
}

View File

@@ -1,4 +1,4 @@
namespace MareSynchronosShared.Authentication;
namespace MareSynchronosServer.Authentication;
internal record SecretKeyFailedAuthorization
{

View File

@@ -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);

View File

@@ -0,0 +1,7 @@
namespace MareSynchronosServer.Hubs;
public static class MareClaimTypes
{
public const string Uid = "uid";
public const string CharaIdent = "character_identification";
}

View File

@@ -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");
}
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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}");

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -6,11 +6,11 @@ using Microsoft.EntityFrameworkCore;
using Discord.WebSocket;
using Prometheus;
using MareSynchronosShared.Models;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Utils;
using MareSynchronosShared.Services;
using static MareSynchronosShared.Protos.IdentificationService;
using static System.Formats.Asn1.AsnWriter;
using Grpc.Net.ClientFactory;
using MareSynchronosShared.Protos;
namespace MareSynchronosServices.Discord;
@@ -30,23 +30,26 @@ public class MareModule : InteractionModuleBase
private readonly DiscordBotServices _botServices;
private readonly IdentificationServiceClient _identificationServiceClient;
private readonly IConfigurationService<ServerConfiguration> _mareClientConfigurationService;
private readonly GrpcClientFactory _grpcClientFactory;
private Random random = new();
public MareModule(ILogger<MareModule> logger, IServiceProvider services, DiscordBotServices botServices,
IdentificationServiceClient identificationServiceClient, IConfigurationService<ServerConfiguration> mareClientConfigurationService)
IdentificationServiceClient identificationServiceClient, IConfigurationService<ServerConfiguration> mareClientConfigurationService,
GrpcClientFactory grpcClientFactory)
{
_logger = logger;
_services = services;
_botServices = botServices;
_identificationServiceClient = identificationServiceClient;
_mareClientConfigurationService = mareClientConfigurationService;
_grpcClientFactory = grpcClientFactory;
}
[SlashCommand("register", "Starts the registration process for the Mare Synchronos server of this Discord")]
public async Task Register([Summary("overwrite", "Overwrites your old account")] bool overwrite = false)
{
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
Context.Client.CurrentUser.Id, nameof(Register),
Context.Interaction.User.Id, nameof(Register),
string.Join(",", new[] { $"{nameof(overwrite)}:{overwrite}" }));
await TryRespondAsync(async () =>
@@ -64,7 +67,7 @@ public class MareModule : InteractionModuleBase
public async Task SetVanityUid([Summary("vanity_uid", "Desired Vanity UID")] string vanityUid)
{
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
Context.Client.CurrentUser.Id, nameof(SetVanityUid),
Context.Interaction.User.Id, nameof(SetVanityUid),
string.Join(",", new[] { $"{nameof(vanityUid)}:{vanityUid}" }));
await TryRespondAsync(async () =>
@@ -83,7 +86,7 @@ public class MareModule : InteractionModuleBase
[Summary("vanity_syncshell_id", "Desired Vanity Syncshell ID")] string vanityId)
{
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
Context.Client.CurrentUser.Id, nameof(SetSyncshellVanityId),
Context.Interaction.User.Id, nameof(SetSyncshellVanityId),
string.Join(",", new[] { $"{nameof(syncshellId)}:{syncshellId}", $"{nameof(vanityId)}:{vanityId}" }));
await TryRespondAsync(async () =>
@@ -100,7 +103,7 @@ public class MareModule : InteractionModuleBase
public async Task Verify()
{
_logger.LogInformation("SlashCommand:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(Verify));
Context.Interaction.User.Id, nameof(Verify));
await TryRespondAsync(async () =>
{
EmbedBuilder eb = new();
@@ -128,7 +131,7 @@ public class MareModule : InteractionModuleBase
public async Task VerifyRelink()
{
_logger.LogInformation("SlashCommand:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(VerifyRelink));
Context.Interaction.User.Id, nameof(VerifyRelink));
await TryRespondAsync(async () =>
{
EmbedBuilder eb = new();
@@ -156,7 +159,7 @@ public class MareModule : InteractionModuleBase
public async Task Recover()
{
_logger.LogInformation("SlashCommand:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(Recover));
Context.Interaction.User.Id, nameof(Recover));
await RespondWithModalAsync<LodestoneModal>("recover_modal").ConfigureAwait(false);
}
@@ -166,7 +169,7 @@ public class MareModule : InteractionModuleBase
[Summary("uid", "ADMIN ONLY: UID to check for")] string? uid = null)
{
_logger.LogInformation("SlashCommand:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(UserInfo));
Context.Interaction.User.Id, nameof(UserInfo));
await TryRespondAsync(async () =>
{
@@ -182,7 +185,7 @@ public class MareModule : InteractionModuleBase
public async Task Relink()
{
_logger.LogInformation("SlashCommand:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(Relink));
Context.Interaction.User.Id, nameof(Relink));
await RespondWithModalAsync<LodestoneModal>("relink_modal").ConfigureAwait(false);
}
@@ -190,7 +193,7 @@ public class MareModule : InteractionModuleBase
public async Task UserAdd([Summary("desired_uid", "Desired UID")] string desiredUid)
{
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
Context.Client.CurrentUser.Id, nameof(UserAdd),
Context.Interaction.User.Id, nameof(UserAdd),
string.Join(",", new[] { $"{nameof(desiredUid)}:{desiredUid}" }));
await TryRespondAsync(async () =>
@@ -201,11 +204,51 @@ public class MareModule : InteractionModuleBase
});
}
[SlashCommand("message", "ADMIN ONLY: sends a message to clients")]
public async Task SendMessageToClients([Summary("message", "Message to send")] string message,
[Summary("severity", "Severity of the message")] MareSynchronosShared.Protos.MessageType messageType = MareSynchronosShared.Protos.MessageType.Info,
[Summary("uid", "User ID to the person to send the message to")] string? uid = null)
{
_logger.LogInformation("SlashCommand:{userId}:{Method}:{message}:{type}:{uid}", Context.Interaction.User.Id, nameof(SendMessageToClients), message, messageType, uid);
using var scope = _services.CreateScope();
using var db = scope.ServiceProvider.GetService<MareDbContext>();
if (!(await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(a => a.DiscordId == Context.Interaction.User.Id))?.User?.IsAdmin ?? true)
{
await RespondAsync("No permission", ephemeral: true).ConfigureAwait(false);
return;
}
if (!string.IsNullOrEmpty(uid) && !await db.Users.AnyAsync(u => u.UID == uid))
{
await RespondAsync("Specified UID does not exist", ephemeral: true).ConfigureAwait(false);
return;
}
try
{
var client = _grpcClientFactory.CreateClient<ClientMessageService.ClientMessageServiceClient>("MessageClient");
await client.SendClientMessageAsync(new ClientMessage()
{
Message = message,
Type = messageType,
Uid = uid ?? string.Empty
});
await RespondAsync("Message sent", ephemeral: true).ConfigureAwait(false);
}
catch (Exception ex)
{
await RespondAsync("Failed to send message: " + ex.ToString(), ephemeral: true).ConfigureAwait(false);
}
}
[ModalInteraction("recover_modal")]
public async Task RecoverModal(LodestoneModal modal)
{
_logger.LogInformation("Modal:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(RecoverModal));
Context.Interaction.User.Id, nameof(RecoverModal));
await TryRespondAsync(async () =>
{
@@ -218,7 +261,7 @@ public class MareModule : InteractionModuleBase
public async Task RegisterModal(LodestoneModal modal)
{
_logger.LogInformation("Modal:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(RegisterModal));
Context.Interaction.User.Id, nameof(RegisterModal));
await TryRespondAsync(async () =>
{
@@ -231,7 +274,7 @@ public class MareModule : InteractionModuleBase
public async Task RelinkModal(LodestoneModal modal)
{
_logger.LogInformation("Modal:{userId}:{Method}",
Context.Client.CurrentUser.Id, nameof(RelinkModal));
Context.Interaction.User.Id, nameof(RelinkModal));
await TryRespondAsync(async () =>
{

View File

@@ -68,6 +68,18 @@ public class Startup
};
});
services.AddGrpcClient<ClientMessageService.ClientMessageServiceClient>("MessageClient", c =>
{
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServicesConfiguration.MainServerGrpcAddress)));
}).ConfigureChannel(c =>
{
c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } };
c.HttpHandler = new SocketsHttpHandler()
{
EnableMultipleHttp2Connections = true
};
});
services.Configure<ServicesConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos"));

View File

@@ -1,3 +0,0 @@
namespace MareSynchronosShared.Authentication;
public record SecretKeyAuthReply(bool Success, string? Uid);

View File

@@ -23,6 +23,22 @@ service ConfigurationService {
rpc GetConfigurationEntry (KeyMessage) returns (ValueMessage);
}
service ClientMessageService {
rpc SendClientMessage (ClientMessage) returns (Empty);
}
message ClientMessage {
MessageType type = 1;
string message = 2;
string uid = 3;
}
enum MessageType {
INFO = 0;
WARNING = 1;
ERROR = 2;
}
message KeyMessage {
string key = 1;
string default = 2;

View File

@@ -1,12 +1,10 @@
using Grpc.Net.Client.Configuration;
using Grpc.Net.ClientFactory;
using MareSynchronosShared.Authentication;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
@@ -45,10 +43,6 @@ public class Startup
services.AddSingleton(m => new MareMetrics(m.GetService<ILogger<MareMetrics>>(), new List<string>
{
MetricsAPI.CounterAuthenticationCacheHits,
MetricsAPI.CounterAuthenticationFailures,
MetricsAPI.CounterAuthenticationRequests,
MetricsAPI.CounterAuthenticationSuccesses
}, new List<string>
{
MetricsAPI.GaugeFilesTotalSize,
@@ -64,7 +58,6 @@ public class Startup
services.AddHostedService(m => m.GetService<FileStatisticsService>());
services.AddHostedService<FileCleanupService>();
services.AddSingleton<SecretKeyAuthenticatorService>();
services.AddDbContextPool<MareDbContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>