diff --git a/Docker/run/compose/mare-standalone.yml b/Docker/run/compose/mare-standalone.yml index ef86e6c..6add106 100644 --- a/Docker/run/compose/mare-standalone.yml +++ b/Docker/run/compose/mare-standalone.yml @@ -53,8 +53,6 @@ services: depends_on: postgres: condition: service_healthy - mare-server: - condition: service_healthy mare-files: image: darkarchon/mare-synchronos-staticfilesserver:latest diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs index 8989341..7fba578 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs @@ -3,6 +3,10 @@ using Microsoft.EntityFrameworkCore; using MareSynchronosServer.Utils; using MareSynchronosShared.Utils; using Microsoft.IdentityModel.Tokens; +using MareSynchronos.API.Data; +using MareSynchronos.API.Dto.Group; +using MareSynchronosShared.Metrics; +using Microsoft.AspNetCore.SignalR; namespace MareSynchronosServer.Hubs; @@ -161,4 +165,111 @@ public partial class MareHub return (string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal), group); } + + private async Task DeleteUser(User user) + { + var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToListAsync().ConfigureAwait(false); + var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == user.UID).ConfigureAwait(false); + var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == user.UID).ConfigureAwait(false); + var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false); + + if (lodestone != null) + { + _dbContext.Remove(lodestone); + } + + while (_dbContext.Files.Any(f => f.Uploader == user)) + { + await Task.Delay(1000).ConfigureAwait(false); + } + + _dbContext.ClientPairs.RemoveRange(ownPairData); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + var otherPairData = await _dbContext.ClientPairs.Include(u => u.User) + .Where(u => u.OtherUser.UID == user.UID).AsNoTracking().ToListAsync().ConfigureAwait(false); + foreach (var pair in otherPairData) + { + await Clients.User(pair.UserUID).Client_UserRemoveClientPair(new(user.ToUserData())).ConfigureAwait(false); + } + + foreach (var pair in groupPairs) + { + await UserLeaveGroup(new GroupDto(new GroupData(pair.GroupGID)), user.UID).ConfigureAwait(false); + } + + _mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1); + + _dbContext.ClientPairs.RemoveRange(otherPairData); + _dbContext.Users.Remove(user); + _dbContext.Auth.Remove(auth); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + private async Task UserLeaveGroup(GroupDto dto, string userUid) + { + _logger.LogCallInfo(MareHubLogger.Args(dto)); + + var (exists, groupPair) = await TryValidateUserInGroup(dto.Group.GID, userUid).ConfigureAwait(false); + if (!exists) return; + + var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == dto.Group.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, userUid, StringComparison.Ordinal)).ToList(); + + _dbContext.GroupPairs.Remove(groupPair); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + + await Clients.User(userUid).Client_GroupDelete(new GroupDto(group.ToGroupData())).ConfigureAwait(false); + + bool ownerHasLeft = string.Equals(group.OwnerUID, userUid, StringComparison.Ordinal); + if (ownerHasLeft) + { + if (!groupPairsWithoutSelf.Any()) + { + _logger.LogCallInfo(MareHubLogger.Args(dto, "Deleted")); + + _dbContext.Groups.Remove(group); + } + else + { + var groupHasMigrated = await SharedDbFunctions.MigrateOrDeleteGroup(_dbContext, group, groupPairsWithoutSelf, _maxExistingGroupsByUser).ConfigureAwait(false); + + if (groupHasMigrated.Item1) + { + _logger.LogCallInfo(MareHubLogger.Args(dto, "Migrated", groupHasMigrated.Item2)); + + var user = await _dbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false); + + await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), + user.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false); + } + else + { + _logger.LogCallInfo(MareHubLogger.Args(dto, "Deleted")); + + await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupDelete(dto).ConfigureAwait(false); + + await SendGroupDeletedToAll(groupPairs).ConfigureAwait(false); + + return; + } + } + } + + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + + _logger.LogCallInfo(MareHubLogger.Args(dto, "Success")); + + await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupPairLeft(new GroupPairDto(dto.Group, groupPair.GroupUser.ToUserData())).ConfigureAwait(false); + + var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); + + var ident = await GetUserIdent(userUid).ConfigureAwait(false); + + foreach (var groupUserPair in groupPairsWithoutSelf) + { + await UserGroupLeave(groupUserPair, allUserPairs, ident, userUid).ConfigureAwait(false); + } + } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs index b40e98f..8638c90 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs @@ -298,68 +298,7 @@ public partial class MareHub [Authorize(Policy = "Identified")] public async Task GroupLeave(GroupDto dto) { - _logger.LogCallInfo(MareHubLogger.Args(dto)); - - var (exists, groupPair) = await TryValidateUserInGroup(dto.Group.GID).ConfigureAwait(false); - if (!exists) return; - - var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == dto.Group.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, UserUID, StringComparison.Ordinal)).ToList(); - - _dbContext.GroupPairs.Remove(groupPair); - await _dbContext.SaveChangesAsync().ConfigureAwait(false); - - await Clients.User(UserUID).Client_GroupDelete(new GroupDto(group.ToGroupData())).ConfigureAwait(false); - - bool ownerHasLeft = string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal); - if (ownerHasLeft) - { - if (!groupPairsWithoutSelf.Any()) - { - _logger.LogCallInfo(MareHubLogger.Args(dto, "Deleted")); - - _dbContext.Groups.Remove(group); - } - else - { - var groupHasMigrated = await SharedDbFunctions.MigrateOrDeleteGroup(_dbContext, group, groupPairsWithoutSelf, _maxExistingGroupsByUser).ConfigureAwait(false); - - if (groupHasMigrated.Item1) - { - _logger.LogCallInfo(MareHubLogger.Args(dto, "Migrated", groupHasMigrated.Item2)); - - var user = await _dbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false); - - await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), - user.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false); - } - else - { - _logger.LogCallInfo(MareHubLogger.Args(dto, "Deleted")); - - await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupDelete(dto).ConfigureAwait(false); - - await SendGroupDeletedToAll(groupPairs).ConfigureAwait(false); - - return; - } - } - } - - await _dbContext.SaveChangesAsync().ConfigureAwait(false); - - _logger.LogCallInfo(MareHubLogger.Args(dto, "Success")); - - await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupPairLeft(new GroupPairDto(dto.Group, groupPair.GroupUser.ToUserData())).ConfigureAwait(false); - - var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); - - foreach (var groupUserPair in groupPairsWithoutSelf) - { - await UserGroupLeave(groupUserPair, allUserPairs, UserCharaIdent).ConfigureAwait(false); - } + await UserLeaveGroup(dto, UserUID).ConfigureAwait(false); } [Authorize(Policy = "Identified")] diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs index df06e17..9df9c46 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs @@ -21,41 +21,13 @@ public partial class MareHub _logger.LogCallInfo(); 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) + var secondaryUsers = await _dbContext.Auth.Include(u => u.User).Where(u => u.PrimaryUserUID == UserUID).Select(c => c.User).ToListAsync().ConfigureAwait(false); + foreach (var user in secondaryUsers) { - _dbContext.Remove(lodestone); + await DeleteUser(user).ConfigureAwait(false); } - while (_dbContext.Files.Any(f => f.Uploader == userEntry)) - { - await Task.Delay(1000).ConfigureAwait(false); - } - - _dbContext.ClientPairs.RemoveRange(ownPairData); - await _dbContext.SaveChangesAsync().ConfigureAwait(false); - var otherPairData = await _dbContext.ClientPairs.Include(u => u.User) - .Where(u => u.OtherUser.UID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false); - foreach (var pair in otherPairData) - { - await Clients.User(pair.UserUID).Client_UserRemoveClientPair(new(userEntry.ToUserData())).ConfigureAwait(false); - } - - foreach (var pair in groupPairs) - { - await GroupLeave(new GroupDto(new GroupData(pair.GroupGID))).ConfigureAwait(false); - } - - _mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1); - - _dbContext.ClientPairs.RemoveRange(otherPairData); - _dbContext.Users.Remove(userEntry); - _dbContext.Auth.Remove(auth); - await _dbContext.SaveChangesAsync().ConfigureAwait(false); + await DeleteUser(userEntry).ConfigureAwait(false); } [Authorize(Policy = "Identified")] diff --git a/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs b/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs index 5901ae2..0a7c4af 100644 --- a/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs +++ b/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs @@ -119,7 +119,15 @@ internal class DiscordBot : IHostedService if (discordUser == null || !discordUser.RoleIds.Any(u => allowedRoleIds.Contains(u))) { _logger.LogInformation($"User {lodestoneAuth.User.UID} not in allowed roles, deleting alias"); - lodestoneAuth.User.Alias = string.Empty; + lodestoneAuth.User.Alias = null; + var secondaryUsers = await db.Auth.Include(u => u.User).Where(u => u.PrimaryUserUID == lodestoneAuth.User.UID).ToListAsync().ConfigureAwait(false); + foreach (var secondaryUser in secondaryUsers) + { + _logger.LogInformation($"Secondary User {secondaryUser.User.UID} not in allowed roles, deleting alias"); + + secondaryUser.User.Alias = null; + db.Update(secondaryUser.User); + } db.Update(lodestoneAuth.User); } diff --git a/MareSynchronosServer/MareSynchronosServices/Discord/MareModule.cs b/MareSynchronosServer/MareSynchronosServices/Discord/MareModule.cs index 5910b76..a3f9a14 100644 --- a/MareSynchronosServer/MareSynchronosServices/Discord/MareModule.cs +++ b/MareSynchronosServer/MareSynchronosServices/Discord/MareModule.cs @@ -52,7 +52,7 @@ public class MareModule : InteractionModuleBase Context.Interaction.User.Id, nameof(Register), string.Join(",", new[] { $"{nameof(overwrite)}:{overwrite}" })); - await TryRespondAsync(async () => + try { if (overwrite) { @@ -60,24 +60,59 @@ public class MareModule : InteractionModuleBase } await RespondWithModalAsync("register_modal").ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } + } + + [SlashCommand("createsecondaryuid", "Creates a new Secret Key to be used for alts")] + public async Task AddSecondary() + { + try + { + var embed = await HandleAddSecondary(Context.User.Id).ConfigureAwait(false); + await RespondAsync(embeds: new[] { embed }, ephemeral: true).ConfigureAwait(false); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [SlashCommand("setvanityuid", "Sets your Vanity UID.")] - public async Task SetVanityUid([Summary("vanity_uid", "Desired Vanity UID")] string vanityUid) + public async Task SetVanityUid([Summary("vanity_uid", "Desired Vanity UID")] string vanityUid, + [Summary("secondary_uid", "Will set the vanity UID for a secondary UID")] string? secondaryUid = null) { _logger.LogInformation("SlashCommand:{userId}:{Method}:{params}", Context.Interaction.User.Id, nameof(SetVanityUid), string.Join(",", new[] { $"{nameof(vanityUid)}:{vanityUid}" })); - await TryRespondAsync(async () => + try { EmbedBuilder eb = new(); - eb = await HandleVanityUid(eb, Context.User.Id, vanityUid); + eb = await HandleVanityUid(eb, Context.User.Id, vanityUid, secondaryUid); await RespondAsync(embeds: new[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [SlashCommand("setsyncshellvanityid", "Sets a Vanity GID for a Syncshell")] @@ -89,14 +124,22 @@ public class MareModule : InteractionModuleBase Context.Interaction.User.Id, nameof(SetSyncshellVanityId), string.Join(",", new[] { $"{nameof(syncshellId)}:{syncshellId}", $"{nameof(vanityId)}:{vanityId}" })); - await TryRespondAsync(async () => + try { EmbedBuilder eb = new(); eb = await HandleVanityGid(eb, Context.User.Id, syncshellId, vanityId); await RespondAsync(embeds: new[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [SlashCommand("verify", "Finishes the registration process for the Mare Synchronos server of this Discord")] @@ -104,7 +147,7 @@ public class MareModule : InteractionModuleBase { _logger.LogInformation("SlashCommand:{userId}:{Method}", Context.Interaction.User.Id, nameof(Verify)); - await TryRespondAsync(async () => + try { EmbedBuilder eb = new(); if (_botServices.VerificationQueue.Any(u => u.Key == Context.User.Id)) @@ -116,7 +159,7 @@ public class MareModule : InteractionModuleBase else if (!_botServices.DiscordLodestoneMapping.ContainsKey(Context.User.Id)) { eb.WithTitle("Cannot verify registration"); - eb.WithDescription("You need to **/register** first before you can **/verify**"); + eb.WithDescription("You need to **/register** first before you can **/verify**" + Environment.NewLine + "If your registration got stuck for some reason, use **/register overwrite:true**"); await RespondAsync(embeds: new[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); } else @@ -124,7 +167,15 @@ public class MareModule : InteractionModuleBase await DeferAsync(ephemeral: true).ConfigureAwait(false); _botServices.VerificationQueue.Enqueue(new KeyValuePair>(Context.User.Id, async (sp) => await HandleVerifyAsync((SocketSlashCommand)Context.Interaction, sp))); } - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [SlashCommand("verify_relink", "Finishes the relink process for your user on the Mare Synchronos server of this Discord")] @@ -132,7 +183,8 @@ public class MareModule : InteractionModuleBase { _logger.LogInformation("SlashCommand:{userId}:{Method}", Context.Interaction.User.Id, nameof(VerifyRelink)); - await TryRespondAsync(async () => + + try { EmbedBuilder eb = new(); if (_botServices.VerificationQueue.Any(u => u.Key == Context.User.Id)) @@ -152,33 +204,51 @@ public class MareModule : InteractionModuleBase await DeferAsync(ephemeral: true).ConfigureAwait(false); _botServices.VerificationQueue.Enqueue(new KeyValuePair>(Context.User.Id, async (sp) => await HandleVerifyRelinkAsync((SocketSlashCommand)Context.Interaction, sp))); } - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } + } [SlashCommand("recover", "Allows you to recover your account by generating a new secret key")] - public async Task Recover() + public async Task Recover([Summary("secondary_uid", "(Optional) Your secondary UID")] string? secondaryUid = null) { _logger.LogInformation("SlashCommand:{userId}:{Method}", Context.Interaction.User.Id, nameof(Recover)); - await RespondWithModalAsync("recover_modal").ConfigureAwait(false); + + await RespondWithModalAsync($"recover_modal,{secondaryUid}").ConfigureAwait(false); } [SlashCommand("userinfo", "Shows you your user information")] - public async Task UserInfo( + public async Task UserInfo([Summary("secondary_uid", "(Optional) Your secondary UID")] string? secondaryUid = null, [Summary("discord_user", "ADMIN ONLY: Discord User to check for")] IUser? discordUser = null, [Summary("uid", "ADMIN ONLY: UID to check for")] string? uid = null) { _logger.LogInformation("SlashCommand:{userId}:{Method}", Context.Interaction.User.Id, nameof(UserInfo)); - await TryRespondAsync(async () => + try { EmbedBuilder eb = new(); - eb = await HandleUserInfo(eb, Context.User.Id, discordUser?.Id ?? null, uid); + eb = await HandleUserInfo(eb, Context.User.Id, secondaryUid, discordUser?.Id ?? null, uid); await RespondAsync(embeds: new[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [SlashCommand("relink", "Allows you to link a new Discord account to an existing Mare account")] @@ -196,12 +266,20 @@ public class MareModule : InteractionModuleBase Context.Interaction.User.Id, nameof(UserAdd), string.Join(",", new[] { $"{nameof(desiredUid)}:{desiredUid}" })); - await TryRespondAsync(async () => + try { var embed = await HandleUserAdd(desiredUid, Context.User.Id); await RespondAsync(embeds: new[] { embed }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [SlashCommand("message", "ADMIN ONLY: sends a message to clients")] @@ -244,17 +322,25 @@ public class MareModule : InteractionModuleBase } } - [ModalInteraction("recover_modal")] - public async Task RecoverModal(LodestoneModal modal) + [ModalInteraction("recover_modal,*")] + public async Task RecoverModal(string? secondaryUid, LodestoneModal modal) { _logger.LogInformation("Modal:{userId}:{Method}", Context.Interaction.User.Id, nameof(RecoverModal)); - await TryRespondAsync(async () => + try { - var embed = await HandleRecoverModalAsync(modal, Context.User.Id).ConfigureAwait(false); + var embed = await HandleRecoverModalAsync(modal, Context.User.Id, secondaryUid).ConfigureAwait(false); await RespondAsync(embeds: new Embed[] { embed }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [ModalInteraction("register_modal")] @@ -263,11 +349,19 @@ public class MareModule : InteractionModuleBase _logger.LogInformation("Modal:{userId}:{Method}", Context.Interaction.User.Id, nameof(RegisterModal)); - await TryRespondAsync(async () => + try { var embed = await HandleRegisterModalAsync(modal, Context.User.Id).ConfigureAwait(false); await RespondAsync(embeds: new Embed[] { embed }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } } [ModalInteraction("relink_modal")] @@ -276,11 +370,79 @@ public class MareModule : InteractionModuleBase _logger.LogInformation("Modal:{userId}:{Method}", Context.Interaction.User.Id, nameof(RelinkModal)); - await TryRespondAsync(async () => + try { var embed = await HandleRelinkModalAsync(modal, Context.User.Id).ConfigureAwait(false); await RespondAsync(embeds: new Embed[] { embed }, ephemeral: true).ConfigureAwait(false); - }); + } + catch (Exception ex) + { + EmbedBuilder eb = new(); + eb.WithTitle("An error occured"); + eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); + + await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); + } + } + + public async Task HandleAddSecondary(ulong discordUserId) + { + var embed = new EmbedBuilder(); + + using var scope = _services.CreateScope(); + using var db = scope.ServiceProvider.GetService(); + + var lodestoneAuth = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(a => a.DiscordId == discordUserId).ConfigureAwait(false); + if (lodestoneAuth == null) + { + embed.WithTitle("Failed to add secondary user"); + embed.WithDescription("You have no registered Mare account yet. Register a Mare account first before trying to add secondary keys."); + return embed.Build(); + } + + var secondaryCount = await db.Auth.CountAsync(u => u.PrimaryUserUID == lodestoneAuth.User.UID).ConfigureAwait(false); + if (await db.Auth.CountAsync(u => u.PrimaryUserUID == lodestoneAuth.User.UID).ConfigureAwait(false) >= 25) + { + embed.WithTitle("Failed to add secondary user"); + embed.WithDescription("You already made 25 secondary UIDs, which is the limit."); + return embed.Build(); + } + + User newUser = new() + { + IsAdmin = false, + IsModerator = false, + LastLoggedIn = DateTime.UtcNow, + }; + + var hasValidUid = false; + while (!hasValidUid) + { + var uid = StringUtils.GenerateRandomString(10); + if (await db.Users.AnyAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false)) continue; + newUser.UID = uid; + hasValidUid = true; + } + + var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString()); + var auth = new Auth() + { + HashedKey = StringUtils.Sha256String(computedHash), + User = newUser, + PrimaryUser = lodestoneAuth.User + }; + + await db.Users.AddAsync(newUser).ConfigureAwait(false); + await db.Auth.AddAsync(auth).ConfigureAwait(false); ; + + await db.SaveChangesAsync().ConfigureAwait(false); + + embed.WithTitle("Secondary UID created"); + embed.AddField("UID", newUser.UID); + embed.AddField("Secret Key", computedHash); + embed.AddField("Secondary UIDs", $"You now have {secondaryCount + 1}/25 secondary UIDs"); + + return embed.Build(); } public async Task HandleUserAdd(string desiredUid, ulong discordUserId) @@ -328,38 +490,24 @@ public class MareModule : InteractionModuleBase return embed.Build(); } - private async Task TryRespondAsync(Action act) - { - try - { - act(); - } - catch (Exception ex) - { - EmbedBuilder eb = new(); - eb.WithTitle("An error occured"); - eb.WithDescription("Please report this error to bug-reports: " + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine); - - await RespondAsync(embeds: new Embed[] { eb.Build() }, ephemeral: true).ConfigureAwait(false); - } - } - - private async Task HandleUserInfo(EmbedBuilder eb, ulong id, ulong? optionalUser = null, string? uid = null) + private async Task HandleUserInfo(EmbedBuilder eb, ulong id, string? secondaryUserUid = null, ulong? optionalUser = null, string? uid = null) { + bool showForSecondaryUser = secondaryUserUid != null; using var scope = _services.CreateScope(); await using var db = scope.ServiceProvider.GetRequiredService(); - var self = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == id).ConfigureAwait(false); + var primaryUser = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == id).ConfigureAwait(false); + ulong userToCheckForDiscordId = id; - if (self == null) + if (primaryUser == null) { eb.WithTitle("No account"); eb.WithDescription("No Mare account was found associated to your Discord user"); return eb; } - bool isAdminCall = self.User.IsModerator || self.User.IsAdmin; + bool isAdminCall = primaryUser.User.IsModerator || primaryUser.User.IsAdmin; if ((optionalUser != null || uid != null) && !isAdminCall) { @@ -391,21 +539,44 @@ public class MareModule : InteractionModuleBase var lodestoneUser = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == userToCheckForDiscordId).ConfigureAwait(false); var dbUser = lodestoneUser.User; - var auth = await db.Auth.SingleOrDefaultAsync(u => u.UserUID == dbUser.UID).ConfigureAwait(false); - var identity = await _connectionMultiplexer.GetDatabase().StringGetAsync("UID:" + dbUser.UID).ConfigureAwait(false); + if (showForSecondaryUser) + { + dbUser = (await db.Auth.Include(u => u.User).SingleOrDefaultAsync(u => u.PrimaryUserUID == dbUser.UID && u.UserUID == secondaryUserUid))?.User; + if (dbUser == null) + { + eb.WithTitle("No such secondary UID"); + eb.WithDescription($"A secondary UID {secondaryUserUid} was not found attached to your primary UID {primaryUser.User.UID}."); + return eb; + } + } + + var auth = await db.Auth.Include(u => u.PrimaryUser).SingleOrDefaultAsync(u => u.UserUID == dbUser.UID).ConfigureAwait(false); var groups = await db.Groups.Where(g => g.OwnerUID == dbUser.UID).ToListAsync().ConfigureAwait(false); var groupsJoined = await db.GroupPairs.Where(g => g.GroupUserUID == dbUser.UID).ToListAsync().ConfigureAwait(false); + var identity = await _connectionMultiplexer.GetDatabase().StringGetAsync("UID:" + dbUser.UID).ConfigureAwait(false); eb.WithTitle("User Information"); - eb.WithDescription("This is the user information for Discord User Id " + userToCheckForDiscordId + Environment.NewLine - + "If you want to verify your secret key is valid, go to https://emn178.github.io/online-tools/sha256.html and copy your secret key into there and compare it to the Hashed Secret Key."); + eb.WithDescription("This is the user information for Discord User <@" + userToCheckForDiscordId + ">" + Environment.NewLine + Environment.NewLine + + "If you want to verify your secret key is valid, go to https://emn178.github.io/online-tools/sha256.html and copy your secret key into there and compare it to the Hashed Secret Key provided below."); eb.AddField("UID", dbUser.UID); if (!string.IsNullOrEmpty(dbUser.Alias)) { eb.AddField("Vanity UID", dbUser.Alias); } + if (showForSecondaryUser) + { + eb.AddField("Primary UID for " + dbUser.UID, auth.PrimaryUserUID); + } + else + { + var secondaryUIDs = await db.Auth.Where(p => p.PrimaryUserUID == dbUser.UID).Select(p => p.UserUID).ToListAsync(); + if (secondaryUIDs.Any()) + { + eb.AddField("Secondary UIDs", string.Join(Environment.NewLine, secondaryUIDs)); + } + } eb.AddField("Last Online (UTC)", dbUser.LastLoggedIn.ToString("U")); - eb.AddField("Currently online: ", !string.IsNullOrEmpty(identity)); + eb.AddField("Currently online ", !string.IsNullOrEmpty(identity)); eb.AddField("Hashed Secret Key", auth.HashedKey); eb.AddField("Joined Syncshells", groupsJoined.Count); eb.AddField("Owned Syncshells", groups.Count); @@ -427,7 +598,7 @@ public class MareModule : InteractionModuleBase return eb; } - private async Task HandleRecoverModalAsync(LodestoneModal arg, ulong userid) + private async Task HandleRecoverModalAsync(LodestoneModal arg, ulong userid, string? secondaryUid) { var embed = new EmbedBuilder(); @@ -449,7 +620,6 @@ public class MareModule : InteractionModuleBase .FirstOrDefaultAsync(a => a.DiscordId == userid && a.HashedLodestoneId == hashedLodestoneId) .ConfigureAwait(false); - // check if discord id or lodestone id is banned if (existingLodestoneAuth == null || existingLodestoneAuth.User == null) { embed.WithTitle("Recovery failed"); @@ -457,18 +627,46 @@ public class MareModule : InteractionModuleBase } else { - var previousAuth = await db.Auth.FirstOrDefaultAsync(u => u.UserUID == existingLodestoneAuth.User.UID); - if (previousAuth != null) + string computedHash = string.Empty; + Auth auth; + if (string.IsNullOrEmpty(secondaryUid)) { - db.Auth.Remove(previousAuth); - } + var previousAuth = await db.Auth.FirstOrDefaultAsync(u => u.UserUID == existingLodestoneAuth.User.UID); + if (previousAuth != null) + { + db.Auth.Remove(previousAuth); + } - var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString()); - var auth = new Auth() + computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString()); + auth = new Auth() + { + HashedKey = StringUtils.Sha256String(computedHash), + User = existingLodestoneAuth.User, + }; + + await db.Auth.AddAsync(auth).ConfigureAwait(false); + } + else { - HashedKey = StringUtils.Sha256String(computedHash), - User = existingLodestoneAuth.User, - }; + var previousAuth = await db.Auth.Include(u => u.User).FirstOrDefaultAsync(u => u.PrimaryUserUID == existingLodestoneAuth.User.UID && u.UserUID == secondaryUid).ConfigureAwait(false); + if (previousAuth == null) + { + embed.WithTitle("Recovery failed"); + embed.WithDescription("This DiscordID has no secondary UID " + secondaryUid); + + return embed.Build(); + } + + db.Auth.Remove(previousAuth); + + computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString()); + auth = new Auth() + { + HashedKey = StringUtils.Sha256String(computedHash), + User = previousAuth.User, + PrimaryUserUID = existingLodestoneAuth.User.UID + }; + } embed.WithTitle("Recovery successful"); embed.WithDescription("This is your new private secret key. Do not share this private secret key with anyone. **If you lose it, it is irrevocably lost.**" @@ -641,7 +839,7 @@ public class MareModule : InteractionModuleBase return lodestoneId; } - private async Task HandleVanityUid(EmbedBuilder eb, ulong id, string newUid) + private async Task HandleVanityUid(EmbedBuilder eb, ulong id, string newUid, string? secondaryUid) { if (_botServices.LastVanityChange.TryGetValue(id, out var lastChange)) { @@ -681,15 +879,38 @@ public class MareModule : InteractionModuleBase return eb; } - var user = lodestoneUser.User; - user.Alias = newUid; - db.Update(user); + if (secondaryUid != null) + { + var secondaryUser = await db.Auth.Include(u => u.PrimaryUser).Include(u => u.User) + .SingleOrDefaultAsync(u => u.UserUID == secondaryUid && u.PrimaryUserUID == lodestoneUser.User.UID).ConfigureAwait(false); + if (secondaryUser == null) + { + eb.WithTitle("No secondary UID found"); + eb.WithDescription($"Did not find a secondary UID {secondaryUid} attached to your primary UID {lodestoneUser.User.UID}"); + return eb; + } + + secondaryUser.User.Alias = newUid; + db.Update(secondaryUser); + + eb.WithTitle("Vanity UID set"); + eb.WithDescription($"Your Vanity UID for the secondary UID {secondaryUid} was set to **{newUid}**." + + Environment.NewLine + "For those changes to apply you will have to reconnect to Mare."); + } + else + { + var user = lodestoneUser.User; + user.Alias = newUid; + db.Update(user); + + eb.WithTitle("Vanity UID set"); + eb.WithDescription("Your Vanity UID was set to **" + newUid + "**." + + Environment.NewLine + "For those changes to apply you will have to reconnect to Mare."); + } + await db.SaveChangesAsync(); _botServices.LastVanityChange[id] = DateTime.UtcNow; - - eb.WithTitle("Vanity UID set"); - eb.WithDescription("Your Vanity UID was set to **" + newUid + "**." + Environment.NewLine + "For those changes to apply you will have to reconnect to Mare."); return eb; } diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20230131193425_AddPrimaryUserToAuth.Designer.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20230131193425_AddPrimaryUserToAuth.Designer.cs new file mode 100644 index 0000000..2baee23 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20230131193425_AddPrimaryUserToAuth.Designer.cs @@ -0,0 +1,544 @@ +// +using System; +using MareSynchronosShared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MareSynchronosServer.Migrations +{ + [DbContext(typeof(MareDbContext))] + [Migration("20230131193425_AddPrimaryUserToAuth")] + partial class AddPrimaryUserToAuth + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => + { + b.Property("HashedKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("hashed_key"); + + b.Property("IsBanned") + .HasColumnType("boolean") + .HasColumnName("is_banned"); + + b.Property("PrimaryUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("primary_user_uid"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("HashedKey") + .HasName("pk_auth"); + + b.HasIndex("PrimaryUserUID") + .HasDatabaseName("ix_auth_primary_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_auth_user_uid"); + + b.ToTable("auth", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Banned", b => + { + b.Property("CharacterIdentification") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("character_identification"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("CharacterIdentification") + .HasName("pk_banned_users"); + + b.ToTable("banned_users", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.BannedRegistrations", b => + { + b.Property("DiscordIdOrLodestoneAuth") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("discord_id_or_lodestone_auth"); + + b.HasKey("DiscordIdOrLodestoneAuth") + .HasName("pk_banned_registrations"); + + b.ToTable("banned_registrations", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("AllowReceivingMessages") + .HasColumnType("boolean") + .HasColumnName("allow_receiving_messages"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_client_pairs"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_client_pairs_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_client_pairs_user_uid"); + + b.ToTable("client_pairs", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.Property("UploadDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("upload_date"); + + b.Property("Uploaded") + .HasColumnType("boolean") + .HasColumnName("uploaded"); + + b.Property("UploaderUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uploader_uid"); + + b.HasKey("Hash") + .HasName("pk_file_caches"); + + b.HasIndex("UploaderUID") + .HasDatabaseName("ix_file_caches_uploader_uid"); + + b.ToTable("file_caches", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ForbiddenUploadEntry", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("ForbiddenBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("forbidden_by"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("Hash") + .HasName("pk_forbidden_upload_entries"); + + b.ToTable("forbidden_upload_entries", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Group", b => + { + b.Property("GID") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("gid"); + + b.Property("Alias") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("alias"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("HashedPassword") + .HasColumnType("text") + .HasColumnName("hashed_password"); + + b.Property("InvitesEnabled") + .HasColumnType("boolean") + .HasColumnName("invites_enabled"); + + b.Property("OwnerUID") + .HasColumnType("character varying(10)") + .HasColumnName("owner_uid"); + + b.HasKey("GID") + .HasName("pk_groups"); + + b.HasIndex("OwnerUID") + .HasDatabaseName("ix_groups_owner_uid"); + + b.ToTable("groups", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("BannedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_user_uid"); + + b.Property("BannedByUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_by_uid"); + + b.Property("BannedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("banned_on"); + + b.Property("BannedReason") + .HasColumnType("text") + .HasColumnName("banned_reason"); + + b.HasKey("GroupGID", "BannedUserUID") + .HasName("pk_group_bans"); + + b.HasIndex("BannedByUID") + .HasDatabaseName("ix_group_bans_banned_by_uid"); + + b.HasIndex("BannedUserUID") + .HasDatabaseName("ix_group_bans_banned_user_uid"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_bans_group_gid"); + + b.ToTable("group_bans", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("GroupUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("group_user_uid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.Property("IsPinned") + .HasColumnType("boolean") + .HasColumnName("is_pinned"); + + b.HasKey("GroupGID", "GroupUserUID") + .HasName("pk_group_pairs"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pairs_group_gid"); + + b.HasIndex("GroupUserUID") + .HasDatabaseName("ix_group_pairs_group_user_uid"); + + b.ToTable("group_pairs", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("Invite") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("invite"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_date"); + + b.HasKey("GroupGID", "Invite") + .HasName("pk_group_temp_invites"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_temp_invites_group_gid"); + + b.HasIndex("Invite") + .HasDatabaseName("ix_group_temp_invites_invite"); + + b.ToTable("group_temp_invites", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => + { + b.Property("DiscordId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("discord_id"); + + b.Property("HashedLodestoneId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("hashed_lodestone_id"); + + b.Property("LodestoneAuthString") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("lodestone_auth_string"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("DiscordId") + .HasName("pk_lodestone_auth"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_lodestone_auth_user_uid"); + + b.ToTable("lodestone_auth", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.User", b => + { + b.Property("UID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uid"); + + b.Property("Alias") + .HasMaxLength(15) + .HasColumnType("character varying(15)") + .HasColumnName("alias"); + + b.Property("IsAdmin") + .HasColumnType("boolean") + .HasColumnName("is_admin"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("LastLoggedIn") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_logged_in"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UID") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => + { + b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") + .WithMany() + .HasForeignKey("PrimaryUserUID") + .HasConstraintName("fk_auth_users_primary_user_temp_id"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_auth_users_user_temp_id1"); + + b.Navigation("PrimaryUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => + { + b.HasOne("MareSynchronosShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_other_user_temp_id2"); + + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_user_temp_id3"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => + { + b.HasOne("MareSynchronosShared.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderUID") + .HasConstraintName("fk_file_caches_users_uploader_uid"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.Group", b => + { + b.HasOne("MareSynchronosShared.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerUID") + .HasConstraintName("fk_groups_users_owner_temp_id8"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => + { + b.HasOne("MareSynchronosShared.Models.User", "BannedBy") + .WithMany() + .HasForeignKey("BannedByUID") + .HasConstraintName("fk_group_bans_users_banned_by_temp_id5"); + + b.HasOne("MareSynchronosShared.Models.User", "BannedUser") + .WithMany() + .HasForeignKey("BannedUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_users_banned_user_temp_id6"); + + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_groups_group_temp_id"); + + b.Navigation("BannedBy"); + + b.Navigation("BannedUser"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_groups_group_temp_id1"); + + b.HasOne("MareSynchronosShared.Models.User", "GroupUser") + .WithMany() + .HasForeignKey("GroupUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_users_group_user_temp_id7"); + + b.Navigation("Group"); + + b.Navigation("GroupUser"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => + { + b.HasOne("MareSynchronosShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_temp_invites_groups_group_gid"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => + { + b.HasOne("MareSynchronosShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_lodestone_auth_users_user_uid"); + + b.Navigation("User"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20230131193425_AddPrimaryUserToAuth.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20230131193425_AddPrimaryUserToAuth.cs new file mode 100644 index 0000000..488bbf0 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20230131193425_AddPrimaryUserToAuth.cs @@ -0,0 +1,210 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MareSynchronosServer.Migrations +{ + /// + public partial class AddPrimaryUserToAuth : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_auth_users_user_temp_id", + table: "auth"); + + migrationBuilder.DropForeignKey( + name: "fk_client_pairs_users_other_user_temp_id1", + table: "client_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_client_pairs_users_user_temp_id2", + table: "client_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_group_bans_users_banned_by_temp_id4", + table: "group_bans"); + + migrationBuilder.DropForeignKey( + name: "fk_group_bans_users_banned_user_temp_id5", + table: "group_bans"); + + migrationBuilder.DropForeignKey( + name: "fk_group_pairs_users_group_user_temp_id6", + table: "group_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_groups_users_owner_temp_id7", + table: "groups"); + + migrationBuilder.AddColumn( + name: "primary_user_uid", + table: "auth", + type: "character varying(10)", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_auth_primary_user_uid", + table: "auth", + column: "primary_user_uid"); + + migrationBuilder.AddForeignKey( + name: "fk_auth_users_primary_user_temp_id", + table: "auth", + column: "primary_user_uid", + principalTable: "users", + principalColumn: "uid"); + + migrationBuilder.AddForeignKey( + name: "fk_auth_users_user_temp_id1", + table: "auth", + column: "user_uid", + principalTable: "users", + principalColumn: "uid"); + + migrationBuilder.AddForeignKey( + name: "fk_client_pairs_users_other_user_temp_id2", + table: "client_pairs", + column: "other_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_client_pairs_users_user_temp_id3", + table: "client_pairs", + column: "user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_group_bans_users_banned_by_temp_id5", + table: "group_bans", + column: "banned_by_uid", + principalTable: "users", + principalColumn: "uid"); + + migrationBuilder.AddForeignKey( + name: "fk_group_bans_users_banned_user_temp_id6", + table: "group_bans", + column: "banned_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_group_pairs_users_group_user_temp_id7", + table: "group_pairs", + column: "group_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_groups_users_owner_temp_id8", + table: "groups", + column: "owner_uid", + principalTable: "users", + principalColumn: "uid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_auth_users_primary_user_temp_id", + table: "auth"); + + migrationBuilder.DropForeignKey( + name: "fk_auth_users_user_temp_id1", + table: "auth"); + + migrationBuilder.DropForeignKey( + name: "fk_client_pairs_users_other_user_temp_id2", + table: "client_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_client_pairs_users_user_temp_id3", + table: "client_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_group_bans_users_banned_by_temp_id5", + table: "group_bans"); + + migrationBuilder.DropForeignKey( + name: "fk_group_bans_users_banned_user_temp_id6", + table: "group_bans"); + + migrationBuilder.DropForeignKey( + name: "fk_group_pairs_users_group_user_temp_id7", + table: "group_pairs"); + + migrationBuilder.DropForeignKey( + name: "fk_groups_users_owner_temp_id8", + table: "groups"); + + migrationBuilder.DropIndex( + name: "ix_auth_primary_user_uid", + table: "auth"); + + migrationBuilder.DropColumn( + name: "primary_user_uid", + table: "auth"); + + migrationBuilder.AddForeignKey( + name: "fk_auth_users_user_temp_id", + table: "auth", + column: "user_uid", + principalTable: "users", + principalColumn: "uid"); + + migrationBuilder.AddForeignKey( + name: "fk_client_pairs_users_other_user_temp_id1", + table: "client_pairs", + column: "other_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_client_pairs_users_user_temp_id2", + table: "client_pairs", + column: "user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_group_bans_users_banned_by_temp_id4", + table: "group_bans", + column: "banned_by_uid", + principalTable: "users", + principalColumn: "uid"); + + migrationBuilder.AddForeignKey( + name: "fk_group_bans_users_banned_user_temp_id5", + table: "group_bans", + column: "banned_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_group_pairs_users_group_user_temp_id6", + table: "group_pairs", + column: "group_user_uid", + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_groups_users_owner_temp_id7", + table: "groups", + column: "owner_uid", + principalTable: "users", + principalColumn: "uid"); + } + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs index e51dece..7b1c4b6 100644 --- a/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/MareDbContextModelSnapshot.cs @@ -33,6 +33,10 @@ namespace MareSynchronosServer.Migrations .HasColumnType("boolean") .HasColumnName("is_banned"); + b.Property("PrimaryUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("primary_user_uid"); + b.Property("UserUID") .HasColumnType("character varying(10)") .HasColumnName("user_uid"); @@ -40,6 +44,9 @@ namespace MareSynchronosServer.Migrations b.HasKey("HashedKey") .HasName("pk_auth"); + b.HasIndex("PrimaryUserUID") + .HasDatabaseName("ix_auth_primary_user_uid"); + b.HasIndex("UserUID") .HasDatabaseName("ix_auth_user_uid"); @@ -402,10 +409,17 @@ namespace MareSynchronosServer.Migrations modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => { + b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") + .WithMany() + .HasForeignKey("PrimaryUserUID") + .HasConstraintName("fk_auth_users_primary_user_temp_id"); + b.HasOne("MareSynchronosShared.Models.User", "User") .WithMany() .HasForeignKey("UserUID") - .HasConstraintName("fk_auth_users_user_temp_id"); + .HasConstraintName("fk_auth_users_user_temp_id1"); + + b.Navigation("PrimaryUser"); b.Navigation("User"); }); @@ -417,14 +431,14 @@ namespace MareSynchronosServer.Migrations .HasForeignKey("OtherUserUID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_client_pairs_users_other_user_temp_id1"); + .HasConstraintName("fk_client_pairs_users_other_user_temp_id2"); b.HasOne("MareSynchronosShared.Models.User", "User") .WithMany() .HasForeignKey("UserUID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_client_pairs_users_user_temp_id2"); + .HasConstraintName("fk_client_pairs_users_user_temp_id3"); b.Navigation("OtherUser"); @@ -446,7 +460,7 @@ namespace MareSynchronosServer.Migrations b.HasOne("MareSynchronosShared.Models.User", "Owner") .WithMany() .HasForeignKey("OwnerUID") - .HasConstraintName("fk_groups_users_owner_temp_id7"); + .HasConstraintName("fk_groups_users_owner_temp_id8"); b.Navigation("Owner"); }); @@ -456,14 +470,14 @@ namespace MareSynchronosServer.Migrations b.HasOne("MareSynchronosShared.Models.User", "BannedBy") .WithMany() .HasForeignKey("BannedByUID") - .HasConstraintName("fk_group_bans_users_banned_by_temp_id4"); + .HasConstraintName("fk_group_bans_users_banned_by_temp_id5"); b.HasOne("MareSynchronosShared.Models.User", "BannedUser") .WithMany() .HasForeignKey("BannedUserUID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_group_bans_users_banned_user_temp_id5"); + .HasConstraintName("fk_group_bans_users_banned_user_temp_id6"); b.HasOne("MareSynchronosShared.Models.Group", "Group") .WithMany() @@ -493,7 +507,7 @@ namespace MareSynchronosServer.Migrations .HasForeignKey("GroupUserUID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_group_pairs_users_group_user_temp_id6"); + .HasConstraintName("fk_group_pairs_users_group_user_temp_id7"); b.Navigation("Group"); diff --git a/MareSynchronosServer/MareSynchronosShared/Models/Auth.cs b/MareSynchronosServer/MareSynchronosShared/Models/Auth.cs index 4d32317..49113aa 100644 --- a/MareSynchronosServer/MareSynchronosShared/Models/Auth.cs +++ b/MareSynchronosServer/MareSynchronosShared/Models/Auth.cs @@ -11,4 +11,6 @@ public class Auth public string UserUID { get; set; } public User User { get; set; } public bool IsBanned { get; set; } + public string? PrimaryUserUID { get; set; } + public User? PrimaryUser { get; set; } }