diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs index adbe44d..fe6ac8f 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs @@ -9,6 +9,8 @@ using LightlessSyncShared.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; using System.Security.Cryptography; namespace LightlessSyncServer.Hubs; @@ -764,6 +766,11 @@ public partial class LightlessHub return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false); } + if (data.ProfileDisabled) + { + return new GroupProfileDto(Group: dto.Group, Description: "This profile was permanently disabled", Tags: [], PictureBase64: null, IsNsfw: false, IsDisabled: true); + } + try { return data.ToDTO(); @@ -792,6 +799,39 @@ public partial class LightlessHub cancellationToken) .ConfigureAwait(false); + + if (!string.IsNullOrEmpty(dto.PictureBase64)) + { + byte[] imageData; + try + { + imageData = Convert.FromBase64String(dto.PictureBase64); + } + catch (FormatException) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "The provided image is not a valid Base64 string.").ConfigureAwait(false); + return; + } + + MemoryStream ms = new(imageData); + await using (ms.ConfigureAwait(false)) + { + var format = await Image.DetectFormatAsync(ms, RequestAbortedToken).ConfigureAwait(false); + if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false); + return; + } + using var image = Image.Load(imageData); + + if (image.Width > 512 || image.Height > 512 || (imageData.Length > 2000 * 1024)) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 512x512 or more than 2MiB").ConfigureAwait(false); + return; + } + } + } + if (groupProfileDb == null) { groupProfileDb = new GroupProfile @@ -800,11 +840,18 @@ public partial class LightlessHub ProfileDisabled = false, IsNSFW = dto.IsNsfw ?? false, }; + groupProfileDb.UpdateProfileFromDto(dto); await DbContext.GroupProfiles.AddAsync(groupProfileDb, cancellationToken).ConfigureAwait(false); } else { + if (groupProfileDb?.ProfileDisabled ?? false) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false); + return; + } + groupProfileDb.UpdateProfileFromDto(dto); var userIds = await DbContext.GroupPairs diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs index efa8c09..838118c 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs @@ -826,16 +826,16 @@ public partial class LightlessHub if (!allUserPairs.Contains(user.User.UID, StringComparer.Ordinal) && !string.Equals(user.User.UID, UserUID, StringComparison.Ordinal)) { - return new UserProfileDto(user.User, false, null, null, "Due to the pause status you cannot access this users profile."); + return new UserProfileDto(user.User, false, null, null, "Due to the pause status you cannot access this users profile.", []); } var data = await DbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.User.UID, cancellationToken: RequestAbortedToken).ConfigureAwait(false); - if (data == null) return new UserProfileDto(user.User, false, null, null, null); + if (data == null) return new UserProfileDto(user.User, false, null, null, null, []); - if (data.FlaggedForReport) return new UserProfileDto(user.User, true, null, null, "This profile is flagged for report and pending evaluation"); - if (data.ProfileDisabled) return new UserProfileDto(user.User, true, null, null, "This profile was permanently disabled"); + if (data.FlaggedForReport) return new UserProfileDto(user.User, true, null, null, "This profile is flagged for report and pending evaluation", []); + if (data.ProfileDisabled) return new UserProfileDto(user.User, true, null, null, "This profile was permanently disabled", []); - return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription); + return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription, data.Tags); } [Authorize(Policy = "Identified")] @@ -913,20 +913,20 @@ public partial class LightlessHub if (profile == null) { - return new UserProfileDto(userData, false, null, null, null); + return new UserProfileDto(userData, false, null, null, null, []); } if (profile.FlaggedForReport) { - return new UserProfileDto(userData, true, null, null, "This profile is flagged for report and pending evaluation"); + return new UserProfileDto(userData, true, null, null, "This profile is flagged for report and pending evaluation", []); } if (profile.ProfileDisabled) { - return new UserProfileDto(userData, true, null, null, "This profile was permanently disabled"); + return new UserProfileDto(userData, true, null, null, "This profile was permanently disabled", []); } - return new UserProfileDto(userData, false, profile.IsNSFW, profile.Base64ProfileImage, profile.UserDescription); + return new UserProfileDto(userData, false, profile.IsNSFW, profile.Base64ProfileImage, profile.UserDescription, profile.Tags); } [Authorize(Policy = "Identified")] @@ -1126,76 +1126,78 @@ public partial class LightlessHub { _logger.LogCallInfo(LightlessHubLogger.Args(dto)); + var cancellationToken = RequestAbortedToken; + if (!string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) throw new HubException("Cannot modify profile data for anyone but yourself"); var existingData = await DbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID, cancellationToken: RequestAbortedToken).ConfigureAwait(false); - if (existingData?.FlaggedForReport ?? false) - { - await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile is currently flagged for report and cannot be edited").ConfigureAwait(false); - return; - } - - if (existingData?.ProfileDisabled ?? false) - { - await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false); - return; - } - + //Image Check of size/format if (!string.IsNullOrEmpty(dto.ProfilePictureBase64)) { - byte[] imageData = Convert.FromBase64String(dto.ProfilePictureBase64); - using MemoryStream ms = new(imageData); - var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false); - if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) + byte[] imageData; + try { - await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false); + imageData = Convert.FromBase64String(dto.ProfilePictureBase64); + } + catch (FormatException) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "The provided image is not a valid Base64 string.").ConfigureAwait(false); return; } - using var image = Image.Load(imageData); - if (image.Width > 256 || image.Height > 256 || (imageData.Length > 250 * 1024)) + MemoryStream ms = new(imageData); + await using (ms.ConfigureAwait(false)) { - await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 256x256 or more than 250KiB.").ConfigureAwait(false); - return; + var format = await Image.DetectFormatAsync(ms, RequestAbortedToken).ConfigureAwait(false); + if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false); + return; + } + using var image = Image.Load(imageData); + + if (image.Width > 512 || image.Height > 512 || (imageData.Length > 2000 * 1024)) + { + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 512x512 or more than 2MiB.").ConfigureAwait(false); + return; + } } } if (existingData != null) { - if (string.Equals("", dto.ProfilePictureBase64, StringComparison.OrdinalIgnoreCase)) + if (existingData.FlaggedForReport) { - existingData.Base64ProfileImage = null; - } - else if (dto.ProfilePictureBase64 != null) - { - existingData.Base64ProfileImage = dto.ProfilePictureBase64; + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile is currently flagged for report and cannot be edited").ConfigureAwait(false); + return; } - if (dto.IsNSFW != null) + if (existingData.ProfileDisabled) { - existingData.IsNSFW = dto.IsNSFW.Value; + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false); + return; } - if (dto.Description != null) - { - existingData.UserDescription = dto.Description; - } + existingData.UpdateProfileFromDto(dto); } else { - UserProfileData userProfileData = new() + UserProfileData newUserProfileData = new() { UserUID = dto.User.UID, Base64ProfileImage = dto.ProfilePictureBase64 ?? null, UserDescription = dto.Description ?? null, - IsNSFW = dto.IsNSFW ?? false + IsNSFW = dto.IsNSFW ?? false, }; - await DbContext.UserProfileData.AddAsync(userProfileData).ConfigureAwait(false); + newUserProfileData.UpdateProfileFromDto(dto); + + await DbContext.UserProfileData.AddAsync(newUserProfileData, cancellationToken).ConfigureAwait(false); } - await DbContext.SaveChangesAsync().ConfigureAwait(false); + + await DbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); diff --git a/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs b/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs index f0cc026..1a1130f 100644 --- a/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs +++ b/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs @@ -2,6 +2,7 @@ using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; +using LightlessSync.API.Dto.User; using LightlessSyncShared.Models; using static LightlessSyncServer.Hubs.LightlessHub; @@ -19,6 +20,16 @@ public static class Extensions if (dto.IsNsfw.HasValue) profile.IsNSFW = dto.IsNsfw.Value; } + public static void UpdateProfileFromDto(this UserProfileData profile, UserProfileDto dto) + { + if (profile == null || dto == null) return; + + profile.Base64ProfileImage = string.IsNullOrWhiteSpace(dto.ProfilePictureBase64) ? null : dto.ProfilePictureBase64; + if (dto.Tags != null) profile.Tags = dto.Tags; + if (dto.Description != null) profile.UserDescription = dto.Description; + if (dto.IsNSFW.HasValue) profile.IsNSFW = dto.IsNSFW.Value; + } + public static GroupProfileDto ToDTO(this GroupProfile groupProfile) { if (groupProfile == null) @@ -38,6 +49,25 @@ public static class Extensions ); } + public static UserProfileDto ToDTO(this UserProfileData userProfileData) + { + if (userProfileData == null) + { + return new UserProfileDto(User: null, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, Description: null, Tags: []); + } + + var userData = userProfileData.User?.ToUserData(); + + return new UserProfileDto( + userData, + userProfileData.ProfileDisabled, + userProfileData.IsNSFW, + userProfileData.Base64ProfileImage, + userProfileData.UserDescription, + userProfileData.Tags + ); + } + public static GroupData ToGroupData(this Group group) { if (group == null)