Compare commits
22 Commits
syncshell-
...
fix-syncsh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc24dc067e | ||
| 6ac56d38c0 | |||
|
|
b7f7381dec | ||
| 1ce7a718bb | |||
|
|
46bb7a4a98 | ||
|
|
8752ce0e62 | ||
|
|
db0115316d | ||
|
|
00d4632510 | ||
|
|
e61e0db36b | ||
|
|
8d82365d0e | ||
|
|
b142329d09 | ||
|
|
8a329ccbaa | ||
|
|
23ee3f98b0 | ||
|
|
f8e711f3c0 | ||
|
|
73e7bb67bb | ||
|
|
70500b21e6 | ||
|
|
698a9eddf7 | ||
| 9cab73e8c8 | |||
| 5240beddf4 | |||
| cb4998e960 | |||
|
|
1ac92f6da2 | ||
|
|
e7e4a4527a |
Submodule LightlessAPI updated: 0bc7abb274...bb92cd477d
@@ -100,14 +100,15 @@ public abstract class AuthControllerBase : Controller
|
||||
|
||||
protected async Task<IActionResult> CreateJwtFromId(string uid, string charaIdent, string alias)
|
||||
{
|
||||
var token = CreateJwt(new List<Claim>()
|
||||
{
|
||||
var token = CreateJwt(
|
||||
[
|
||||
new Claim(LightlessClaimTypes.Uid, uid),
|
||||
new Claim(LightlessClaimTypes.CharaIdent, charaIdent),
|
||||
new Claim(LightlessClaimTypes.Alias, alias),
|
||||
new Claim(LightlessClaimTypes.Expires, DateTime.UtcNow.AddHours(6).Ticks.ToString(CultureInfo.InvariantCulture)),
|
||||
new Claim(LightlessClaimTypes.Continent, await _geoIPProvider.GetCountryFromIP(HttpAccessor))
|
||||
});
|
||||
new Claim(LightlessClaimTypes.Continent, await _geoIPProvider.GetContinentFromIP(HttpAccessor)),
|
||||
new Claim(LightlessClaimTypes.Country, await _geoIPProvider.GetCountryFromIP(HttpAccessor)),
|
||||
]);
|
||||
|
||||
return Content(token.RawData);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using LightlessSync.API.Routes;
|
||||
using LightlessSyncAuthService.Services;
|
||||
using LightlessSyncAuthService.Utils;
|
||||
using LightlessSyncShared;
|
||||
using LightlessSyncShared.Data;
|
||||
using LightlessSyncShared.Services;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using LightlessSync.API.Routes;
|
||||
using LightlessSyncAuthService.Services;
|
||||
using LightlessSyncAuthService.Utils;
|
||||
using LightlessSyncShared;
|
||||
using LightlessSyncShared.Data;
|
||||
using LightlessSyncShared.Services;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using LightlessSyncShared;
|
||||
using LightlessSyncAuthService.Utils;
|
||||
using LightlessSyncShared.Services;
|
||||
using LightlessSyncShared.Utils.Configuration;
|
||||
using MaxMind.GeoIP2;
|
||||
using System.Net;
|
||||
|
||||
namespace LightlessSyncAuthService.Services;
|
||||
|
||||
@@ -23,7 +24,7 @@ public class GeoIPService : IHostedService
|
||||
_lightlessConfiguration = lightlessConfiguration;
|
||||
}
|
||||
|
||||
public async Task<string> GetCountryFromIP(IHttpContextAccessor httpContextAccessor)
|
||||
public async Task<string> GetContinentFromIP(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
if (!_useGeoIP)
|
||||
{
|
||||
@@ -32,7 +33,9 @@ public class GeoIPService : IHostedService
|
||||
|
||||
try
|
||||
{
|
||||
var ip = httpContextAccessor.GetIpAddress();
|
||||
var ip = httpContextAccessor.GetClientIpAddress();
|
||||
if (ip is null || IPAddress.IsLoopback(ip))
|
||||
return "*";
|
||||
|
||||
using CancellationTokenSource waitCts = new();
|
||||
waitCts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
@@ -41,6 +44,7 @@ public class GeoIPService : IHostedService
|
||||
if (_dbReader!.TryCity(ip, out var response))
|
||||
{
|
||||
string? continent = response?.Continent.Code;
|
||||
|
||||
if (!string.IsNullOrEmpty(continent) &&
|
||||
string.Equals(continent, "NA", StringComparison.Ordinal)
|
||||
&& response?.Location.Longitude != null)
|
||||
@@ -140,4 +144,34 @@ public class GeoIPService : IHostedService
|
||||
_dbReader?.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal async Task<string> GetCountryFromIP(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
if (!_useGeoIP)
|
||||
return "*";
|
||||
|
||||
var ip = httpContextAccessor.GetClientIpAddress();
|
||||
if (ip is null || IPAddress.IsLoopback(ip))
|
||||
return "*";
|
||||
|
||||
try
|
||||
{
|
||||
using CancellationTokenSource waitCts = new(TimeSpan.FromSeconds(5));
|
||||
while (_processingReload)
|
||||
await Task.Delay(100, waitCts.Token).ConfigureAwait(false);
|
||||
|
||||
if (_dbReader!.TryCity(ip, out var response))
|
||||
{
|
||||
var country = response?.Country?.IsoCode;
|
||||
return country ?? "*";
|
||||
}
|
||||
|
||||
return "*";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "GeoIP lookup failed for {Ip}", ip);
|
||||
return "*";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Net;
|
||||
|
||||
namespace LightlessSyncAuthService.Utils
|
||||
{
|
||||
public static class HttpContextAccessorExtensions
|
||||
{
|
||||
public static IPAddress? GetClientIpAddress(this IHttpContextAccessor accessor)
|
||||
{
|
||||
var context = accessor.HttpContext;
|
||||
if (context == null) return null;
|
||||
|
||||
string[] headerKeys = { "CF-Connecting-IP", "X-Forwarded-For", "X-Real-IP" };
|
||||
foreach (var key in headerKeys)
|
||||
{
|
||||
if (context.Request.Headers.TryGetValue(key, out var values))
|
||||
{
|
||||
var ipCandidate = values.FirstOrDefault()?.Split(',').FirstOrDefault()?.Trim();
|
||||
if (IPAddress.TryParse(ipCandidate, out var parsed))
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return context.Connection?.RemoteIpAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Dto.Group;
|
||||
using LightlessSyncServer.Models;
|
||||
using LightlessSyncServer.Utils;
|
||||
using LightlessSyncShared.Metrics;
|
||||
using LightlessSyncShared.Models;
|
||||
@@ -8,7 +9,6 @@ using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace LightlessSyncServer.Hubs;
|
||||
|
||||
@@ -20,6 +20,8 @@ public partial class LightlessHub
|
||||
|
||||
public string Continent => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, LightlessClaimTypes.Continent, StringComparison.Ordinal))?.Value ?? "UNK";
|
||||
|
||||
public string Country => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, LightlessClaimTypes.Country, StringComparison.Ordinal))?.Value ?? "UNK";
|
||||
|
||||
private async Task DeleteUser(User user)
|
||||
{
|
||||
var ownPairData = await DbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
@@ -209,7 +211,8 @@ public partial class LightlessHub
|
||||
|
||||
if (isOwnerResult.ReferredGroup == null) return (false, null);
|
||||
|
||||
var groupPairSelf = await DbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid || g.Group.Alias == gid && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
var groupPairSelf = await DbContext.GroupPairs.SingleOrDefaultAsync(
|
||||
g => (g.GroupGID == gid || g.Group.Alias == gid) && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||
if (groupPairSelf == null || !groupPairSelf.IsModerator) return (false, null);
|
||||
|
||||
return (true, isOwnerResult.ReferredGroup);
|
||||
|
||||
@@ -3,14 +3,18 @@ using LightlessSync.API.Data.Enum;
|
||||
using LightlessSync.API.Data.Extensions;
|
||||
using LightlessSync.API.Dto.Group;
|
||||
using LightlessSync.API.Dto.User;
|
||||
using LightlessSyncServer.Models;
|
||||
using LightlessSyncServer.Services;
|
||||
using LightlessSyncServer.Utils;
|
||||
using LightlessSyncShared.Models;
|
||||
using LightlessSyncShared.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace LightlessSyncServer.Hubs;
|
||||
@@ -750,7 +754,7 @@ public partial class LightlessHub
|
||||
if (dto?.Group == null)
|
||||
{
|
||||
_logger.LogCallWarning(LightlessHubLogger.Args("GroupGetProfile: dto.Group is null"));
|
||||
return new GroupProfileDto(Group: null, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
return new GroupProfileDto(Group: null, Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
}
|
||||
|
||||
var data = await DbContext.GroupProfiles
|
||||
@@ -763,12 +767,12 @@ public partial class LightlessHub
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, BannerBase64: 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);
|
||||
return new GroupProfileDto(Group: dto.Group, Description: "This profile was permanently disabled", Tags: [], PictureBase64: null, BannerBase64: null, IsNsfw: false, IsDisabled: true);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -778,7 +782,7 @@ public partial class LightlessHub
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogCallWarning(LightlessHubLogger.Args(ex, "GroupGetProfile: failed to map GroupProfileDto for {Group}", dto.Group.GID ?? dto.Group.AliasOrGID));
|
||||
return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,64 +799,64 @@ public partial class LightlessHub
|
||||
if (!hasRights) return;
|
||||
|
||||
var groupProfileDb = await DbContext.GroupProfiles
|
||||
.FirstOrDefaultAsync(g => g.Group.GID == dto.Group.GID || g.Group.Alias == dto.Group.GID,
|
||||
cancellationToken)
|
||||
.Include(g => g.Group)
|
||||
.FirstOrDefaultAsync(g => g.GroupGID == dto.Group.GID, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImageCheckService.ImageLoadResult profileResult = null;
|
||||
ImageCheckService.ImageLoadResult bannerResult = null;
|
||||
|
||||
//Avatar image validation
|
||||
if (!string.IsNullOrEmpty(dto.PictureBase64))
|
||||
{
|
||||
byte[] imageData;
|
||||
try
|
||||
profileResult = await ImageCheckService.ValidateImageAsync(dto.PictureBase64, banner: false, RequestAbortedToken).ConfigureAwait(false);
|
||||
|
||||
if (!profileResult.Success)
|
||||
{
|
||||
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);
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, profileResult.ErrorMessage).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryStream ms = new(imageData);
|
||||
await using (ms.ConfigureAwait(false))
|
||||
//Banner image validation
|
||||
if (!string.IsNullOrEmpty(dto.BannerBase64))
|
||||
{
|
||||
bannerResult = await ImageCheckService.ValidateImageAsync(dto.BannerBase64, banner: true, RequestAbortedToken).ConfigureAwait(false);
|
||||
|
||||
if (!bannerResult.Success)
|
||||
{
|
||||
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<Rgba32>(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;
|
||||
}
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, bannerResult.ErrorMessage).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var sanitizedProfileImage = profileResult?.Base64Image;
|
||||
var sanitizedBannerImage = bannerResult?.Base64Image;
|
||||
|
||||
if (groupProfileDb == null)
|
||||
{
|
||||
groupProfileDb = new GroupProfile
|
||||
{
|
||||
GroupGID = dto.Group.GID,
|
||||
Group = group,
|
||||
ProfileDisabled = false,
|
||||
IsNSFW = dto.IsNsfw ?? false,
|
||||
};
|
||||
|
||||
groupProfileDb.UpdateProfileFromDto(dto);
|
||||
groupProfileDb.UpdateProfileFromDto(dto, sanitizedProfileImage, sanitizedBannerImage);
|
||||
await DbContext.GroupProfiles.AddAsync(groupProfileDb, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
groupProfileDb.Group ??= group;
|
||||
|
||||
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);
|
||||
groupProfileDb.UpdateProfileFromDto(dto, sanitizedProfileImage, sanitizedBannerImage);
|
||||
|
||||
var userIds = await DbContext.GroupPairs
|
||||
.Where(p => p.GroupGID == groupProfileDb.GroupGID)
|
||||
|
||||
@@ -3,6 +3,8 @@ using LightlessSync.API.Data.Enum;
|
||||
using LightlessSync.API.Data.Extensions;
|
||||
using LightlessSync.API.Dto.Group;
|
||||
using LightlessSync.API.Dto.User;
|
||||
using LightlessSyncServer.Models;
|
||||
using LightlessSyncServer.Services;
|
||||
using LightlessSyncServer.Utils;
|
||||
using LightlessSyncShared.Metrics;
|
||||
using LightlessSyncShared.Models;
|
||||
@@ -204,8 +206,8 @@ public partial class LightlessHub
|
||||
return;
|
||||
}
|
||||
|
||||
var sender = await _pairService.TryAddPairAsync(UserUID, payload.UID);
|
||||
var receiver = await _pairService.TryAddPairAsync(payload.UID, UserUID);
|
||||
var sender = await _pairService.TryAddPairAsync(UserUID, payload.UID).ConfigureAwait(false);
|
||||
var receiver = await _pairService.TryAddPairAsync(payload.UID, UserUID).ConfigureAwait(false);
|
||||
|
||||
var user = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
var otherUser = await DbContext.Users.SingleAsync(u => u.UID == payload.UID).ConfigureAwait(false);
|
||||
@@ -304,7 +306,6 @@ public partial class LightlessHub
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task NotifyBroadcastOwnerOfPairRequest(string targetHashedCid)
|
||||
{
|
||||
var myHashedCid = UserCharaIdent;
|
||||
@@ -360,23 +361,6 @@ public partial class LightlessHub
|
||||
|
||||
await Clients.User(entry.OwnerUID).Client_ReceiveBroadcastPairRequest(dto).ConfigureAwait(false);
|
||||
}
|
||||
private class PairingPayload
|
||||
{
|
||||
public string UID { get; set; } = string.Empty;
|
||||
public string HashedCid { get; set; } = string.Empty;
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
|
||||
public class BroadcastRedisEntry
|
||||
{
|
||||
public string HashedCID { get; set; } = string.Empty;
|
||||
public string OwnerUID { get; set; } = string.Empty;
|
||||
public string? GID { get; set; }
|
||||
|
||||
public bool OwnedBy(string userUid) => !string.IsNullOrEmpty(userUid) && string.Equals(OwnerUID, userUid, StringComparison.Ordinal);
|
||||
|
||||
public bool HasOwner() => !string.IsNullOrEmpty(OwnerUID);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task SetBroadcastStatus(bool enabled, GroupBroadcastRequestDto? groupDto = null)
|
||||
@@ -826,16 +810,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, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: "Due to the pause status you cannot access this users profile.", Tags: []);
|
||||
}
|
||||
|
||||
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, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: null, Tags: []);
|
||||
|
||||
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, Disabled: true, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: "This profile is flagged for report and pending evaluation", Tags: []);
|
||||
if (data.ProfileDisabled) return new UserProfileDto(user.User, Disabled: true, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: "This profile was permanently disabled", Tags: []);
|
||||
|
||||
return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription, data.Tags);
|
||||
return data.ToDTO();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
@@ -913,20 +897,20 @@ public partial class LightlessHub
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
return new UserProfileDto(userData, false, null, null, null, []);
|
||||
return new UserProfileDto(userData, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: null, Tags: []);
|
||||
}
|
||||
|
||||
if (profile.FlaggedForReport)
|
||||
{
|
||||
return new UserProfileDto(userData, true, null, null, "This profile is flagged for report and pending evaluation", []);
|
||||
return new UserProfileDto(userData, Disabled: true, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: "This profile is flagged for report and pending evaluation", Tags: []);
|
||||
}
|
||||
|
||||
if (profile.ProfileDisabled)
|
||||
{
|
||||
return new UserProfileDto(userData, true, null, null, "This profile was permanently disabled", []);
|
||||
return new UserProfileDto(userData, Disabled: true, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: "This profile was permanently disabled", Tags: []);
|
||||
}
|
||||
|
||||
return new UserProfileDto(userData, false, profile.IsNSFW, profile.Base64ProfileImage, profile.UserDescription, profile.Tags);
|
||||
return profile.ToDTO();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
@@ -959,22 +943,36 @@ public partial class LightlessHub
|
||||
}
|
||||
|
||||
bool hadInvalidData = false;
|
||||
List<string> invalidGamePaths = new();
|
||||
List<string> invalidFileSwapPaths = new();
|
||||
List<string> invalidGamePaths = [];
|
||||
List<string> invalidFileSwapPaths = [];
|
||||
|
||||
var gamePathRegex = GamePathRegex();
|
||||
var hashRegex = HashRegex();
|
||||
|
||||
foreach (var replacement in dto.CharaData.FileReplacements.SelectMany(p => p.Value))
|
||||
{
|
||||
var invalidPaths = replacement.GamePaths.Where(p => !GamePathRegex().IsMatch(p)).ToList();
|
||||
invalidPaths.AddRange(replacement.GamePaths.Where(p => !AllowedExtensionsForGamePaths.Any(e => p.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||
replacement.GamePaths = replacement.GamePaths.Where(p => !invalidPaths.Contains(p, StringComparer.OrdinalIgnoreCase)).ToArray();
|
||||
bool validGamePaths = replacement.GamePaths.Any();
|
||||
bool validHash = string.IsNullOrEmpty(replacement.Hash) || HashRegex().IsMatch(replacement.Hash);
|
||||
bool validFileSwapPath = string.IsNullOrEmpty(replacement.FileSwapPath) || GamePathRegex().IsMatch(replacement.FileSwapPath);
|
||||
if (!validGamePaths || !validHash || !validFileSwapPath)
|
||||
var validGamePaths = replacement.GamePaths
|
||||
.Where(p => gamePathRegex.IsMatch(p) &&
|
||||
AllowedExtensionsForGamePaths.Any(e => p.EndsWith(e, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToArray();
|
||||
|
||||
var invalidPaths = replacement.GamePaths.Except(validGamePaths, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
|
||||
replacement.GamePaths = validGamePaths;
|
||||
|
||||
bool validHash = string.IsNullOrEmpty(replacement.Hash) || hashRegex.IsMatch(replacement.Hash);
|
||||
bool validFileSwapPath = string.IsNullOrEmpty(replacement.FileSwapPath) || gamePathRegex.IsMatch(replacement.FileSwapPath);
|
||||
bool validGamePathsFlag = validGamePaths.Length
|
||||
!= 0;
|
||||
|
||||
if (!validGamePathsFlag || !validHash || !validFileSwapPath)
|
||||
{
|
||||
_logger.LogCallWarning(LightlessHubLogger.Args("Invalid Data", "GamePaths", validGamePaths, string.Join(",", invalidPaths), "Hash", validHash, replacement.Hash, "FileSwap", validFileSwapPath, replacement.FileSwapPath));
|
||||
_logger.LogCallWarning(LightlessHubLogger.Args("Invalid Data", "GamePaths", validGamePathsFlag, string.Join(',', invalidPaths), "Hash", validHash, replacement.Hash, "FileSwap", validFileSwapPath, replacement.FileSwapPath));
|
||||
|
||||
hadInvalidData = true;
|
||||
|
||||
if (!validFileSwapPath) invalidFileSwapPaths.Add(replacement.FileSwapPath);
|
||||
if (!validGamePaths) invalidGamePaths.AddRange(replacement.GamePaths);
|
||||
if (!validGamePathsFlag) invalidGamePaths.AddRange(invalidPaths);
|
||||
if (!validHash) invalidFileSwapPaths.Add(replacement.Hash);
|
||||
}
|
||||
}
|
||||
@@ -1132,36 +1130,30 @@ public partial class LightlessHub
|
||||
|
||||
var existingData = await DbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID, cancellationToken: RequestAbortedToken).ConfigureAwait(false);
|
||||
|
||||
//Image Check of size/format
|
||||
ImageCheckService.ImageLoadResult profileResult = new();
|
||||
ImageCheckService.ImageLoadResult bannerResult = new();
|
||||
|
||||
//Avatar image validation
|
||||
if (!string.IsNullOrEmpty(dto.ProfilePictureBase64))
|
||||
{
|
||||
byte[] imageData;
|
||||
try
|
||||
profileResult = await ImageCheckService.ValidateImageAsync(dto.ProfilePictureBase64, banner: false, RequestAbortedToken).ConfigureAwait(false);
|
||||
|
||||
if (!profileResult.Success)
|
||||
{
|
||||
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);
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, profileResult.ErrorMessage).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryStream ms = new(imageData);
|
||||
await using (ms.ConfigureAwait(false))
|
||||
//Banner image validation
|
||||
if (!string.IsNullOrEmpty(dto.BannerPictureBase64))
|
||||
{
|
||||
bannerResult = await ImageCheckService.ValidateImageAsync(dto.BannerPictureBase64, banner: true, RequestAbortedToken).ConfigureAwait(false);
|
||||
|
||||
if (!bannerResult.Success)
|
||||
{
|
||||
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<Rgba32>(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;
|
||||
}
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, bannerResult.ErrorMessage).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1179,7 +1171,7 @@ public partial class LightlessHub
|
||||
return;
|
||||
}
|
||||
|
||||
existingData.UpdateProfileFromDto(dto);
|
||||
existingData.UpdateProfileFromDto(dto, profileResult.Base64Image, bannerResult.Base64Image);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1191,7 +1183,7 @@ public partial class LightlessHub
|
||||
IsNSFW = dto.IsNSFW ?? false,
|
||||
};
|
||||
|
||||
newUserProfileData.UpdateProfileFromDto(dto);
|
||||
existingData.UpdateProfileFromDto(dto, profileResult.Base64Image, bannerResult.Base64Image);
|
||||
|
||||
await DbContext.UserProfileData.AddAsync(newUserProfileData, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis.Extensions.Core.Abstractions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace LightlessSyncServer.Hubs;
|
||||
|
||||
@@ -161,8 +160,13 @@ public partial class LightlessHub : Hub<ILightlessHub>, ILightlessHub
|
||||
}
|
||||
else
|
||||
{
|
||||
_lightlessMetrics.IncGaugeWithLabels(MetricsAPI.GaugeConnections, labels: Continent);
|
||||
var ResultLabels = new List<string>
|
||||
{
|
||||
Continent,
|
||||
Country,
|
||||
};
|
||||
|
||||
_lightlessMetrics.IncGaugeWithLabels(MetricsAPI.GaugeConnections, labels: [.. ResultLabels]);
|
||||
try
|
||||
{
|
||||
_logger.LogCallInfo(LightlessHubLogger.Args(_contextAccessor.GetIpAddress(), Context.ConnectionId, UserCharaIdent));
|
||||
@@ -185,7 +189,12 @@ public partial class LightlessHub : Hub<ILightlessHub>, ILightlessHub
|
||||
if (_userConnections.TryGetValue(UserUID, out var connectionId)
|
||||
&& string.Equals(connectionId, Context.ConnectionId, StringComparison.Ordinal))
|
||||
{
|
||||
_lightlessMetrics.DecGaugeWithLabels(MetricsAPI.GaugeConnections, labels: Continent);
|
||||
var ResultLabels = new List<string>
|
||||
{
|
||||
Continent,
|
||||
Country,
|
||||
};
|
||||
_lightlessMetrics.DecGaugeWithLabels(MetricsAPI.GaugeConnections, labels: [.. ResultLabels]);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace LightlessSyncServer.Models;
|
||||
|
||||
public class BroadcastRedisEntry()
|
||||
{
|
||||
public string? GID { get; set; }
|
||||
public string HashedCID { get; set; } = string.Empty;
|
||||
public string OwnerUID { get; set; } = string.Empty;
|
||||
|
||||
public bool OwnedBy(string userUid) => !string.IsNullOrEmpty(userUid) && string.Equals(OwnerUID, userUid, StringComparison.Ordinal);
|
||||
|
||||
public bool HasOwner() => !string.IsNullOrEmpty(OwnerUID);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace LightlessSyncServer.Models;
|
||||
|
||||
public class PairingPayload
|
||||
{
|
||||
public string UID { get; set; } = string.Empty;
|
||||
public string HashedCid { get; set; } = string.Empty;
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
@@ -41,7 +41,6 @@ public class Program
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugeUsersRegistered, context.Users.AsNoTracking().Count());
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugePairs, context.ClientPairs.AsNoTracking().Count());
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, context.Permissions.AsNoTracking().Where(p=>p.IsPaused).Count());
|
||||
|
||||
}
|
||||
|
||||
if (args.Length == 0 || !string.Equals(args[0], "dry", StringComparison.Ordinal))
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace LightlessSyncServer.Services
|
||||
{
|
||||
public class ImageCheckService
|
||||
{
|
||||
private static readonly int _imageWidthAvatar = 512;
|
||||
private static readonly int _imageHeightAvatar = 512;
|
||||
private static readonly int _imageWidthBanner = 840;
|
||||
private static readonly int _imageHeightBanner = 260;
|
||||
private static readonly int _imageSize = 2000;
|
||||
|
||||
public class ImageLoadResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public string? Base64Image { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
public static ImageLoadResult Fail(string message) => new()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = message,
|
||||
};
|
||||
|
||||
public static ImageLoadResult Ok(string base64) => new()
|
||||
{
|
||||
Success = true,
|
||||
Base64Image = base64,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static async Task<ImageLoadResult> ValidateImageAsync(string base64String, bool banner, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return ImageLoadResult.Fail("Operation cancelled.");
|
||||
|
||||
byte[] imageData;
|
||||
try
|
||||
{
|
||||
imageData = Convert.FromBase64String(base64String);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return ImageLoadResult.Fail("The provided image is not a valid Base64 string.");
|
||||
}
|
||||
|
||||
Image<Rgba32>? image = null;
|
||||
bool imageLoaded = false;
|
||||
IImageFormat? format = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var ms = new MemoryStream(imageData))
|
||||
{
|
||||
format = await Image.DetectFormatAsync(ms, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (format == null)
|
||||
{
|
||||
return ImageLoadResult.Fail("Unable to detect image format.");
|
||||
}
|
||||
|
||||
using (image = Image.Load<Rgba32>(imageData))
|
||||
{
|
||||
imageLoaded = true;
|
||||
|
||||
int maxWidth = banner ? _imageWidthBanner : _imageWidthAvatar;
|
||||
int maxHeight = banner ? _imageHeightBanner : _imageHeightAvatar;
|
||||
|
||||
if (image.Width > maxWidth || image.Height > maxHeight)
|
||||
{
|
||||
var ratio = Math.Min((double)maxWidth / image.Width, (double)maxHeight / image.Height);
|
||||
int newWidth = (int)(image.Width * ratio);
|
||||
int newHeight = (int)(image.Height * ratio);
|
||||
|
||||
image.Mutate(x => x.Resize(newWidth, newHeight));
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
await image.SaveAsPngAsync(memoryStream, token).ConfigureAwait(false);
|
||||
|
||||
if (memoryStream.Length > _imageSize * 1024)
|
||||
{
|
||||
return ImageLoadResult.Fail("Your image exceeds 2 MiB after resizing/conversion.");
|
||||
}
|
||||
|
||||
string base64Png = Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
|
||||
return ImageLoadResult.Ok(base64Png);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (imageLoaded)
|
||||
image?.Dispose();
|
||||
|
||||
return ImageLoadResult.Fail("Failed to load or process the image. It may be corrupted or unsupported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using LightlessSync.API.Dto;
|
||||
using LightlessSync.API.SignalR;
|
||||
using LightlessSyncServer.Hubs;
|
||||
using LightlessSyncServer.Models;
|
||||
using LightlessSyncShared.Data;
|
||||
using LightlessSyncShared.Metrics;
|
||||
using LightlessSyncShared.Services;
|
||||
using LightlessSyncShared.Utils.Configuration;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using StackExchange.Redis.Extensions.Core.Abstractions;
|
||||
using static LightlessSyncServer.Hubs.LightlessHub;
|
||||
|
||||
namespace LightlessSyncServer.Services;
|
||||
|
||||
|
||||
@@ -10,21 +10,29 @@ namespace LightlessSyncServer.Utils;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static void UpdateProfileFromDto(this GroupProfile profile, GroupProfileDto dto)
|
||||
public static void UpdateProfileFromDto(this GroupProfile profile, GroupProfileDto dto, string? base64PictureString = null, string? base64BannerString = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(profile);
|
||||
ArgumentNullException.ThrowIfNull(dto);
|
||||
|
||||
if (profile == null || dto == null) return;
|
||||
|
||||
profile.Base64GroupProfileImage = string.IsNullOrWhiteSpace(dto.PictureBase64) ? null : dto.PictureBase64;
|
||||
profile.Base64GroupProfileImage = string.IsNullOrWhiteSpace(base64PictureString) ? null : base64PictureString;
|
||||
profile.Base64GroupBannerImage = string.IsNullOrWhiteSpace(base64BannerString) ? null : base64BannerString;
|
||||
if (dto.Tags != null) profile.Tags = dto.Tags;
|
||||
if (dto.Description != null) profile.Description = dto.Description;
|
||||
if (dto.IsNsfw.HasValue) profile.IsNSFW = dto.IsNsfw.Value;
|
||||
}
|
||||
|
||||
public static void UpdateProfileFromDto(this UserProfileData profile, UserProfileDto dto)
|
||||
public static void UpdateProfileFromDto(this UserProfileData profile, UserProfileDto dto, string? base64PictureString, string? base64BannerString = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(profile);
|
||||
ArgumentNullException.ThrowIfNull(dto);
|
||||
|
||||
if (profile == null || dto == null) return;
|
||||
|
||||
profile.Base64ProfileImage = string.IsNullOrWhiteSpace(dto.ProfilePictureBase64) ? null : dto.ProfilePictureBase64;
|
||||
profile.Base64ProfileImage = string.IsNullOrWhiteSpace(base64PictureString) ? null : base64PictureString;
|
||||
profile.Base64BannerImage = string.IsNullOrWhiteSpace(base64BannerString) ? null : base64BannerString;
|
||||
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;
|
||||
@@ -34,16 +42,18 @@ public static class Extensions
|
||||
{
|
||||
if (groupProfile == null)
|
||||
{
|
||||
return new GroupProfileDto(Group: null, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
return new GroupProfileDto(Group: null, Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: false, IsDisabled: false);
|
||||
}
|
||||
|
||||
var groupData = groupProfile.Group?.ToGroupData();
|
||||
var groupData = groupProfile.Group?.ToGroupData()
|
||||
?? (!string.IsNullOrWhiteSpace(groupProfile.GroupGID) ? new GroupData(groupProfile.GroupGID) : null);
|
||||
|
||||
return new GroupProfileDto(
|
||||
groupData,
|
||||
groupProfile.Description,
|
||||
groupProfile.Tags,
|
||||
groupProfile.Base64GroupProfileImage,
|
||||
groupProfile.Base64GroupBannerImage,
|
||||
groupProfile.IsNSFW,
|
||||
groupProfile.ProfileDisabled
|
||||
);
|
||||
@@ -53,7 +63,7 @@ public static class Extensions
|
||||
{
|
||||
if (userProfileData == null)
|
||||
{
|
||||
return new UserProfileDto(User: null, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, Description: null, Tags: []);
|
||||
return new UserProfileDto(User: null, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, Description: null, Tags: []);
|
||||
}
|
||||
|
||||
var userData = userProfileData.User?.ToUserData();
|
||||
@@ -63,6 +73,7 @@ public static class Extensions
|
||||
userProfileData.ProfileDisabled,
|
||||
userProfileData.IsNSFW,
|
||||
userProfileData.Base64ProfileImage,
|
||||
userProfileData.Base64BannerImage,
|
||||
userProfileData.UserDescription,
|
||||
userProfileData.Tags
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using LightlessSync.API.SignalR;
|
||||
using LightlessSyncServer.Hubs;
|
||||
using LightlessSyncServer.Hubs;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LightlessSyncServer.Utils;
|
||||
@@ -20,7 +20,7 @@ public class LightlessMetrics
|
||||
if (!string.Equals(gauge, MetricsAPI.GaugeConnections, StringComparison.OrdinalIgnoreCase))
|
||||
_gauges.Add(gauge, Prometheus.Metrics.CreateGauge(gauge, gauge));
|
||||
else
|
||||
_gauges.Add(gauge, Prometheus.Metrics.CreateGauge(gauge, gauge, new[] { "continent" }));
|
||||
_gauges.Add(gauge, Prometheus.Metrics.CreateGauge(gauge, gauge, ["continent", "country"]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace LightlessSyncServer.Migrations
|
||||
type: "integer[]",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.Sql("UPDATE group_profiles SET tags = NULL;");
|
||||
migrationBuilder.Sql(
|
||||
"ALTER TABLE group_profiles ALTER COLUMN tags TYPE integer[] USING string_to_array(tags, ',')::integer[];"
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<int[]>(
|
||||
name: "tags",
|
||||
|
||||
1189
LightlessSyncServer/LightlessSyncShared/Migrations/20251020181150_AddBannerForProfiles.Designer.cs
generated
Normal file
1189
LightlessSyncServer/LightlessSyncShared/Migrations/20251020181150_AddBannerForProfiles.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LightlessSyncServer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddBannerForProfiles : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "base64banner_image",
|
||||
table: "user_profile_data",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "base64group_banner_image",
|
||||
table: "group_profiles",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "base64banner_image",
|
||||
table: "user_profile_data");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "base64group_banner_image",
|
||||
table: "group_profiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,6 +589,10 @@ namespace LightlessSyncServer.Migrations
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("group_gid");
|
||||
|
||||
b.Property<string>("Base64GroupBannerImage")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("base64group_banner_image");
|
||||
|
||||
b.Property<string>("Base64GroupProfileImage")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("base64group_profile_image");
|
||||
@@ -824,6 +828,10 @@ namespace LightlessSyncServer.Migrations
|
||||
.HasColumnType("character varying(10)")
|
||||
.HasColumnName("user_uid");
|
||||
|
||||
b.Property<string>("Base64BannerImage")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("base64banner_image");
|
||||
|
||||
b.Property<string>("Base64ProfileImage")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("base64profile_image");
|
||||
|
||||
@@ -15,6 +15,7 @@ public class GroupProfile
|
||||
public string Description { get; set; }
|
||||
public int[] Tags { get; set; }
|
||||
public string Base64GroupProfileImage { get; set; }
|
||||
public string Base64GroupBannerImage { get; set; }
|
||||
public bool IsNSFW { get; set; } = false;
|
||||
public bool ProfileDisabled { get; set; } = false;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace LightlessSyncShared.Models;
|
||||
public class UserProfileData
|
||||
{
|
||||
public string Base64ProfileImage { get; set; }
|
||||
public string Base64BannerImage { get; set; }
|
||||
public bool FlaggedForReport { get; set; }
|
||||
public bool IsNSFW { get; set; }
|
||||
public bool ProfileDisabled { get; set; }
|
||||
|
||||
@@ -8,6 +8,7 @@ public static class LightlessClaimTypes
|
||||
public const string Internal = "internal";
|
||||
public const string Expires = "expiration_date";
|
||||
public const string Continent = "continent";
|
||||
public const string Country = "country";
|
||||
public const string DiscordUser = "discord_user";
|
||||
public const string DiscordId = "discord_user_id";
|
||||
public const string OAuthLoginToken = "oauth_login_token";
|
||||
|
||||
@@ -211,7 +211,7 @@ public class Startup
|
||||
}
|
||||
else
|
||||
{
|
||||
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(CacheController), typeof(RequestController), typeof(SpeedTestController)));
|
||||
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(CacheController), typeof(ShardServerFilesController), typeof(RequestController), typeof(SpeedTestController)));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -236,7 +236,6 @@ public class Startup
|
||||
}).AddJwtBearer();
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
|
||||
options.AddPolicy("Internal", new AuthorizationPolicyBuilder().RequireClaim(LightlessClaimTypes.Internal, "true").Build());
|
||||
});
|
||||
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
||||
|
||||
Reference in New Issue
Block a user