From ed13ee8921c785f01f717e1242486dcc3ce9bf5f Mon Sep 17 00:00:00 2001 From: azyges <229218900+azyges@users.noreply.github.com> Date: Mon, 29 Sep 2025 05:31:58 +0900 Subject: [PATCH] lightfinder config, securing methods with stricter checking and added pair request notifications --- LightlessAPI | 2 +- .../Configuration/BroadcastConfiguration.cs | 74 ++++++++ .../Configuration/BroadcastOptions.cs | 23 +++ .../Configuration/IBroadcastConfiguration.cs | 16 ++ .../Hubs/LightlessHub.Groups.cs | 25 ++- .../Hubs/LightlessHub.User.cs | 172 ++++++++++++++++-- .../LightlessSyncServer/Hubs/LightlessHub.cs | 7 +- .../LightlessSyncServer/Startup.cs | 3 + .../LightlessSyncServer/appsettings.json | 9 + 9 files changed, 311 insertions(+), 20 deletions(-) create mode 100644 LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs create mode 100644 LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs create mode 100644 LightlessSyncServer/LightlessSyncServer/Configuration/IBroadcastConfiguration.cs diff --git a/LightlessAPI b/LightlessAPI index 5bfd21a..69f0e31 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 5bfd21aaa90817f14c9e2931e77b20f4276f16ed +Subproject commit 69f0e310bd78e0c56eab298199e6e2ca15bf56bd diff --git a/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs new file mode 100644 index 0000000..5b796aa --- /dev/null +++ b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs @@ -0,0 +1,74 @@ +using System; +using Microsoft.Extensions.Options; + +namespace LightlessSyncServer.Configuration; + +public class BroadcastConfiguration : IBroadcastConfiguration +{ + private static readonly TimeSpan DefaultEntryTtl = TimeSpan.FromMinutes(5); + private const int DefaultMaxStatusBatchSize = 30; + private const string DefaultNotificationTemplate = "{DisplayName} sent you a pair request. To accept, right-click them, open the context menu, and send a request back."; + + private readonly IOptionsMonitor _optionsMonitor; + + public BroadcastConfiguration(IOptionsMonitor optionsMonitor) + { + _optionsMonitor = optionsMonitor; + } + + private BroadcastOptions Options => _optionsMonitor.CurrentValue ?? new BroadcastOptions(); + + public string RedisKeyPrefix + { + get + { + var prefix = Options.RedisKeyPrefix; + return string.IsNullOrWhiteSpace(prefix) ? "broadcast:" : prefix!; + } + } + + public TimeSpan BroadcastEntryTtl + { + get + { + var seconds = Options.EntryTtlSeconds; + return seconds > 0 ? TimeSpan.FromSeconds(seconds) : DefaultEntryTtl; + } + } + + public int MaxStatusBatchSize + { + get + { + var value = Options.MaxStatusBatchSize; + return value > 0 ? value : DefaultMaxStatusBatchSize; + } + } + + public bool NotifyOwnerOnPairRequest => Options.NotifyOwnerOnPairRequest; + + public bool EnableBroadcasting => Options.EnableBroadcasting; + + public bool EnableSyncshellBroadcastPayloads => Options.EnableSyncshellBroadcastPayloads; + + public string BuildRedisKey(string hashedCid) + { + if (string.IsNullOrEmpty(hashedCid)) + return RedisKeyPrefix; + + return string.Concat(RedisKeyPrefix, hashedCid); + } + + public string BuildPairRequestNotification(string displayName) + { + var template = Options.PairRequestNotificationTemplate; + if (string.IsNullOrWhiteSpace(template)) + { + template = DefaultNotificationTemplate; + } + + displayName = string.IsNullOrWhiteSpace(displayName) ? "Someone" : displayName; + + return template.Replace("{DisplayName}", displayName, StringComparison.Ordinal); + } +} diff --git a/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs new file mode 100644 index 0000000..960c8ca --- /dev/null +++ b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; + +namespace LightlessSyncServer.Configuration; + +public class BroadcastOptions +{ + [Required] + public string RedisKeyPrefix { get; set; } = "broadcast:"; + + [Range(1, int.MaxValue)] + public int EntryTtlSeconds { get; set; } = 300; + + [Range(1, int.MaxValue)] + public int MaxStatusBatchSize { get; set; } = 30; + + public bool NotifyOwnerOnPairRequest { get; set; } = true; + + public bool EnableBroadcasting { get; set; } = true; + + public bool EnableSyncshellBroadcastPayloads { get; set; } = true; + + public string PairRequestNotificationTemplate { get; set; } = "{DisplayName} sent you a pair request. To accept, right-click them, open the context menu, and send a request back."; +} diff --git a/LightlessSyncServer/LightlessSyncServer/Configuration/IBroadcastConfiguration.cs b/LightlessSyncServer/LightlessSyncServer/Configuration/IBroadcastConfiguration.cs new file mode 100644 index 0000000..7320720 --- /dev/null +++ b/LightlessSyncServer/LightlessSyncServer/Configuration/IBroadcastConfiguration.cs @@ -0,0 +1,16 @@ +using System; + +namespace LightlessSyncServer.Configuration; + +public interface IBroadcastConfiguration +{ + string RedisKeyPrefix { get; } + TimeSpan BroadcastEntryTtl { get; } + int MaxStatusBatchSize { get; } + bool NotifyOwnerOnPairRequest { get; } + bool EnableBroadcasting { get; } + bool EnableSyncshellBroadcastPayloads { get; } + + string BuildRedisKey(string hashedCid); + string BuildPairRequestNotification(string displayName); +} diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs index 9713831..1204590 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs @@ -1,4 +1,4 @@ -using LightlessSync.API.Data; +using LightlessSync.API.Data; using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; @@ -925,6 +925,13 @@ public partial class LightlessHub return false; } + if (!_broadcastConfiguration.EnableBroadcasting || !_broadcastConfiguration.EnableSyncshellBroadcastPayloads) + { + _logger.LogCallWarning(LightlessHubLogger.Args("syncshell broadcast disabled", "User", UserUID, "GID", dto.GID)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Syncshell broadcasting is currently disabled.").ConfigureAwait(false); + return false; + } + var (isOwner, _) = await TryValidateOwner(dto.GID).ConfigureAwait(false); if (!isOwner) { @@ -941,6 +948,9 @@ public partial class LightlessHub { _logger.LogCallInfo(LightlessHubLogger.Args("Requested Syncshells", broadcastEntries.Select(b => b.GID))); + if (!_broadcastConfiguration.EnableBroadcasting || !_broadcastConfiguration.EnableSyncshellBroadcastPayloads) + return new List(); + var results = new List(); var gidsToValidate = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -949,10 +959,19 @@ public partial class LightlessHub if (string.IsNullOrWhiteSpace(entry.HashedCID) || string.IsNullOrWhiteSpace(entry.GID)) continue; - var redisKey = $"broadcast:{entry.HashedCID}"; + var redisKey = _broadcastConfiguration.BuildRedisKey(entry.HashedCID); var redisEntry = await _redis.GetAsync(redisKey).ConfigureAwait(false); - if (redisEntry?.GID != null && string.Equals(redisEntry.GID, entry.GID, StringComparison.OrdinalIgnoreCase)) + if (redisEntry is null) + continue; + + if (!string.IsNullOrEmpty(redisEntry.HashedCID) && !string.Equals(redisEntry.HashedCID, entry.HashedCID, StringComparison.Ordinal)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("mismatched broadcast cid for group lookup", "Requested", entry.HashedCID, "EntryCID", redisEntry.HashedCID)); + continue; + } + + if (redisEntry.GID != null && string.Equals(redisEntry.GID, entry.GID, StringComparison.OrdinalIgnoreCase)) gidsToValidate.Add(entry.GID); } diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs index ddd0b1b..3248a00 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs @@ -4,14 +4,17 @@ using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.User; using LightlessSyncServer.Utils; +using LightlessSyncServer.Configuration; using LightlessSyncShared.Metrics; using LightlessSyncShared.Models; +using LightlessSyncShared.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using StackExchange.Redis; +using System.Linq; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; @@ -252,9 +255,62 @@ public partial class LightlessHub await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, $"Pair request sent. Waiting for the other player to confirm.").ConfigureAwait(false); _logger.LogCallInfo(LightlessHubLogger.Args("stored pairing request", myCid, otherCid)); + await NotifyBroadcastOwnerOfPairRequest(otherCid).ConfigureAwait(false); } } + + private async Task NotifyBroadcastOwnerOfPairRequest(string targetHashedCid) + { + if (string.IsNullOrWhiteSpace(targetHashedCid)) + return; + + if (!_broadcastConfiguration.EnableBroadcasting || !_broadcastConfiguration.NotifyOwnerOnPairRequest) + return; + + var db = _redis.Database; + var broadcastKey = _broadcastConfiguration.BuildRedisKey(targetHashedCid); + RedisValueWithExpiry broadcastValue; + + try + { + broadcastValue = await db.StringGetWithExpiryAsync(broadcastKey).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogCallWarning(LightlessHubLogger.Args("failed to fetch broadcast for pair notify", "CID", targetHashedCid, "Error", ex)); + return; + } + + if (broadcastValue.Value.IsNullOrEmpty || broadcastValue.Expiry is null || broadcastValue.Expiry <= TimeSpan.Zero) + return; + + BroadcastRedisEntry? entry; + try + { + entry = JsonSerializer.Deserialize(broadcastValue.Value!); + } + catch (Exception ex) + { + _logger.LogCallWarning(LightlessHubLogger.Args("failed to deserialize broadcast for pair notify", "CID", targetHashedCid, "Value", broadcastValue.Value, "Error", ex)); + return; + } + + if (entry is null || !string.Equals(entry.HashedCID, targetHashedCid, StringComparison.Ordinal)) + return; + + if (!entry.HasOwner()) + return; + + if (string.Equals(entry.OwnerUID, UserUID, StringComparison.Ordinal)) + return; + + var senderAlias = Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, LightlessClaimTypes.Alias, StringComparison.Ordinal))?.Value; + var displayName = string.IsNullOrWhiteSpace(senderAlias) ? UserUID : senderAlias; + var message = _broadcastConfiguration.BuildPairRequestNotification(displayName); + + await Clients.User(entry.OwnerUID).Client_ReceiveServerMessage(MessageSeverity.Information, message).ConfigureAwait(false); + } private class PairingPayload { public string UID { get; set; } = string.Empty; @@ -264,14 +320,26 @@ public partial class LightlessHub 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(string hashedCid, bool enabled, GroupBroadcastRequestDto? groupDto = null) { + if (enabled && !_broadcastConfiguration.EnableBroadcasting) + { + _logger.LogCallWarning(LightlessHubLogger.Args("broadcast disabled", UserUID, "CID", hashedCid)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Broadcasting is currently disabled.").ConfigureAwait(false); + return; + } + var db = _redis.Database; - var broadcastKey = $"broadcast:{hashedCid}"; + var broadcastKey = _broadcastConfiguration.BuildRedisKey(hashedCid); if (enabled) { @@ -279,6 +347,13 @@ public partial class LightlessHub if (groupDto is not null) { + if (!_broadcastConfiguration.EnableSyncshellBroadcastPayloads) + { + _logger.LogCallWarning(LightlessHubLogger.Args("syncshell broadcast disabled", UserUID, "CID", hashedCid, "GID", groupDto.GID)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Syncshell broadcasting is currently disabled.").ConfigureAwait(false); + return; + } + groupDto.HashedCID = hashedCid; var valid = await SetGroupBroadcastStatus(groupDto).ConfigureAwait(false); @@ -288,14 +363,36 @@ public partial class LightlessHub gid = groupDto.GID; } + BroadcastRedisEntry? existingEntry = null; + var existingValue = await db.StringGetAsync(broadcastKey).ConfigureAwait(false); + if (!existingValue.IsNullOrEmpty) + { + try + { + existingEntry = JsonSerializer.Deserialize(existingValue!); + } + catch (Exception ex) + { + _logger.LogCallWarning(LightlessHubLogger.Args("failed to deserialize broadcast entry during enable", "CID", hashedCid, "Value", existingValue, "Error", ex)); + } + + if (existingEntry is not null && existingEntry.HasOwner() && !existingEntry.OwnedBy(UserUID)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("unauthorized attempt to take broadcast ownership", UserUID, "CID", hashedCid, "ExistingOwner", existingEntry.OwnerUID)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Another user is already broadcasting with that CID."); + return; + } + } + var entry = new BroadcastRedisEntry { HashedCID = hashedCid, + OwnerUID = UserUID, GID = gid, }; var json = JsonSerializer.Serialize(entry); - await db.StringSetAsync(broadcastKey, json, TimeSpan.FromMinutes(5)).ConfigureAwait(false); + await db.StringSetAsync(broadcastKey, json, _broadcastConfiguration.BroadcastEntryTtl).ConfigureAwait(false); _logger.LogCallInfo(LightlessHubLogger.Args("broadcast enabled", hashedCid, "GID", gid)); } else @@ -315,13 +412,20 @@ public partial class LightlessHub return; } - if (entry is null || entry.HashedCID != hashedCid) + if (entry is null || !string.Equals(entry.HashedCID, hashedCid, StringComparison.Ordinal)) { _logger.LogCallWarning(LightlessHubLogger.Args("unauthorized attempt to remove broadcast", UserUID, "CID", hashedCid, "Stored", entry?.HashedCID)); await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "You can only disable your own broadcast. :3"); return; } + if (entry.HasOwner() && !entry.OwnedBy(UserUID)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("unauthorized attempt to remove broadcast", UserUID, "CID", hashedCid, "Owner", entry.OwnerUID)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "You can only disable your own broadcast. :3"); + return; + } + await db.KeyDeleteAsync(broadcastKey).ConfigureAwait(false); _logger.LogCallInfo(LightlessHubLogger.Args("broadcast disabled", hashedCid, "GID", entry.GID)); } @@ -331,8 +435,11 @@ public partial class LightlessHub [Authorize(Policy = "Identified")] public async Task IsUserBroadcasting(string hashedCid) { + if (!_broadcastConfiguration.EnableBroadcasting) + return null; + var db = _redis.Database; - var key = $"broadcast:{hashedCid}"; + var key = _broadcastConfiguration.BuildRedisKey(hashedCid); var result = await db.StringGetWithExpiryAsync(key).ConfigureAwait(false); if (result.Expiry is null || result.Expiry <= TimeSpan.Zero || result.Value.IsNullOrEmpty) @@ -348,6 +455,12 @@ public partial class LightlessHub return null; } + if (entry is not null && !string.Equals(entry.HashedCID, hashedCid, StringComparison.Ordinal)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("mismatched broadcast entry", "CID", hashedCid, "EntryCID", entry.HashedCID)); + return null; + } + var dto = new BroadcastStatusInfoDto { HashedCID = entry?.HashedCID ?? hashedCid, @@ -363,8 +476,11 @@ public partial class LightlessHub [Authorize(Policy = "Identified")] public async Task GetBroadcastTtl(string hashedCid) { + if (!_broadcastConfiguration.EnableBroadcasting) + return null; + var db = _redis.Database; - var key = $"broadcast:{hashedCid}"; + var key = _broadcastConfiguration.BuildRedisKey(hashedCid); var value = await db.StringGetAsync(key).ConfigureAwait(false); if (value.IsNullOrEmpty) @@ -381,9 +497,21 @@ public partial class LightlessHub return null; } - if (entry?.HashedCID != hashedCid) + if (entry is null) { - _logger.LogCallWarning(LightlessHubLogger.Args("unauthorized ttl query", UserUID, "CID", hashedCid, "EntryCID", entry?.HashedCID)); + _logger.LogCallWarning(LightlessHubLogger.Args("missing broadcast entry during ttl query", "CID", hashedCid)); + return null; + } + + if (!string.Equals(entry.HashedCID, hashedCid, StringComparison.Ordinal)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("unauthorized ttl query", UserUID, "CID", hashedCid, "EntryCID", entry.HashedCID)); + return null; + } + + if (entry.HasOwner() && !entry.OwnedBy(UserUID)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("unauthorized ttl query", UserUID, "CID", hashedCid, "Owner", entry.OwnerUID)); return null; } @@ -396,19 +524,25 @@ public partial class LightlessHub } - private const int MaxBatchSize = 30; - [Authorize(Policy = "Identified")] - public async Task AreUsersBroadcasting(List hashedCids) + public async Task AreUsersBroadcasting(List hashedCids) { + if (!_broadcastConfiguration.EnableBroadcasting) + { + _logger.LogCallInfo(LightlessHubLogger.Args("batch broadcast disabled", "Count", hashedCids.Count)); + return null; + } + + var maxBatchSize = _broadcastConfiguration.MaxStatusBatchSize; + if (hashedCids.Count > maxBatchSize) + hashedCids = hashedCids.Take(maxBatchSize).ToList(); + var db = _redis.Database; - if (hashedCids.Count > MaxBatchSize) - hashedCids = hashedCids.Take(MaxBatchSize).ToList(); var tasks = new Dictionary>(hashedCids.Count); foreach (var cid in hashedCids) { - var key = $"broadcast:{cid}"; + var key = _broadcastConfiguration.BuildRedisKey(cid); tasks[cid] = db.StringGetWithExpiryAsync(key); } @@ -433,7 +567,17 @@ public partial class LightlessHub try { entry = JsonSerializer.Deserialize(raw!); - gid = entry?.GID; + if (entry is not null && !string.Equals(entry.HashedCID, cid, StringComparison.Ordinal)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("mismatched broadcast cid in batch", "Requested", cid, "EntryCID", entry.HashedCID)); + entry = null; + gid = null; + isBroadcasting = false; + } + else + { + gid = entry?.GID; + } } catch (Exception ex) { diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs index 901a263..402e8be 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs @@ -1,8 +1,9 @@ -using LightlessSync.API.Data; +using LightlessSync.API.Data; using LightlessSync.API.Data.Enum; using LightlessSync.API.Dto; using LightlessSync.API.SignalR; using LightlessSyncServer.Services; +using LightlessSyncServer.Configuration; using LightlessSyncServer.Utils; using LightlessSyncShared; using LightlessSyncShared.Data; @@ -29,6 +30,7 @@ public partial class LightlessHub : Hub, ILightlessHub private readonly LightlessHubLogger _logger; private readonly string _shardName; private readonly int _maxExistingGroupsByUser; + private readonly IBroadcastConfiguration _broadcastConfiguration; private readonly int _maxJoinedGroupsByUser; private readonly int _maxGroupUserCount; private readonly IRedisDatabase _redis; @@ -46,7 +48,7 @@ public partial class LightlessHub : Hub, ILightlessHub IDbContextFactory lightlessDbContextFactory, ILogger logger, SystemInfoService systemInfoService, IConfigurationService configuration, IHttpContextAccessor contextAccessor, IRedisDatabase redisDb, OnlineSyncedPairCacheService onlineSyncedPairCacheService, LightlessCensus lightlessCensus, - GPoseLobbyDistributionService gPoseLobbyDistributionService, PairService pairService) + GPoseLobbyDistributionService gPoseLobbyDistributionService, IBroadcastConfiguration broadcastConfiguration, PairService pairService) { _lightlessMetrics = lightlessMetrics; _systemInfoService = systemInfoService; @@ -65,6 +67,7 @@ public partial class LightlessHub : Hub, ILightlessHub _gPoseLobbyDistributionService = gPoseLobbyDistributionService; _logger = new LightlessHubLogger(this, logger); _dbContextLazy = new Lazy(() => lightlessDbContextFactory.CreateDbContext()); + _broadcastConfiguration = broadcastConfiguration; _pairService = pairService; } diff --git a/LightlessSyncServer/LightlessSyncServer/Startup.cs b/LightlessSyncServer/LightlessSyncServer/Startup.cs index 5b0b672..b647e85 100644 --- a/LightlessSyncServer/LightlessSyncServer/Startup.cs +++ b/LightlessSyncServer/LightlessSyncServer/Startup.cs @@ -2,6 +2,7 @@ using AspNetCoreRateLimit; using LightlessSync.API.SignalR; using LightlessSyncAuthService.Controllers; using LightlessSyncServer.Controllers; +using LightlessSyncServer.Configuration; using LightlessSyncServer.Hubs; using LightlessSyncServer.Services; using LightlessSyncShared.Data; @@ -87,7 +88,9 @@ public class Startup services.Configure(Configuration.GetRequiredSection("LightlessSync")); services.Configure(Configuration.GetRequiredSection("LightlessSync")); + services.Configure(Configuration.GetSection("Broadcast")); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/LightlessSyncServer/LightlessSyncServer/appsettings.json b/LightlessSyncServer/LightlessSyncServer/appsettings.json index 880bf73..80524cb 100644 --- a/LightlessSyncServer/LightlessSyncServer/appsettings.json +++ b/LightlessSyncServer/LightlessSyncServer/appsettings.json @@ -29,6 +29,15 @@ "ServiceAddress": "http://localhost:5002", "StaticFileServiceAddress": "http://localhost:5003" }, + "Broadcast": { + "RedisKeyPrefix": "broadcast:", + "EntryTtlSeconds": 10800, + "MaxStatusBatchSize": 30, + "NotifyOwnerOnPairRequest": true, + "EnableBroadcasting": true, + "EnableSyncshellBroadcastPayloads": true, + "PairRequestNotificationTemplate": "{DisplayName} sent you a pair request. To accept, right-click them, open the context menu, and send a request back." + }, "AllowedHosts": "*", "Kestrel": { "Endpoints": {