mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-19 15:51:39 +00:00
Improve file formal and clear program self
This commit is contained in:
1 parent
e1285972d7
commit
490a59be62
40 files changed
+1495
-511
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -193,5 +193,44 @@
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.PlayerStats.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.playerlog",
|
||||
"name": "Player Log",
|
||||
"description": "Logs all player activity (chat, kills, meetings, joins, leaves) and provides an admin panel to view, search, and export logs.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.PlayerLog.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.monitor",
|
||||
"name": "Monitor",
|
||||
"description": "Provides /api/monitor/status and /api/monitor/health endpoints for server health checks and uptime monitoring.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.Monitor.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.privacy",
|
||||
"name": "Privacy Policy",
|
||||
"description": "Serves a customizable privacy policy page at /privacy with an admin panel editor.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.Privacy.dll"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>Empostor.Plugins.Monitor</AssemblyName>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyToMarketplace" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="..\..\marketplace" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -9,7 +8,7 @@ using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Net.Manager;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Empostor.Server.Http;
|
||||
namespace Empostor.Plugins.Monitor;
|
||||
|
||||
[ApiController]
|
||||
public sealed class MonitorController : ControllerBase
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.Monitor;
|
||||
|
||||
[EmpostorPlugin("gg.empostor.monitor")]
|
||||
public sealed class MonitorPlugin : PluginBase
|
||||
{
|
||||
private readonly ILogger<MonitorPlugin> _logger;
|
||||
|
||||
public MonitorPlugin(ILogger<MonitorPlugin> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override ValueTask EnableAsync()
|
||||
{
|
||||
_logger.LogInformation("[Monitor] Enabled. Status API at /api/monitor/status, health at /api/monitor/health");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override ValueTask DisableAsync() => default;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Empostor.Plugins.Monitor;
|
||||
|
||||
public sealed class MonitorPluginStartup : IPluginStartup, IPluginHttpStartup
|
||||
{
|
||||
public void ConfigureHost(IHostBuilder host) { }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddApplicationPart(typeof(MonitorPluginStartup).Assembly);
|
||||
}
|
||||
|
||||
public void ConfigureWebApplication(IApplicationBuilder app)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"BouncyCastle.Cryptography": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Features": "2.2.0",
|
||||
"System.Text.Encodings.Web": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Features": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.6",
|
||||
"contentHash": "0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"System.Diagnostics.DiagnosticSource": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
|
||||
},
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>Empostor.Plugins.PlayerLog</AssemblyName>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyToMarketplace" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="..\..\marketplace" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Linq;
|
||||
using Empostor.Api.Admin;
|
||||
|
||||
namespace Empostor.Plugins.PlayerLog;
|
||||
|
||||
public sealed class PlayerLogAdminExtension : IAdminExtension
|
||||
{
|
||||
private readonly PlayerLogStore _store;
|
||||
|
||||
public PlayerLogAdminExtension(PlayerLogStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
public string Id => "player-log";
|
||||
|
||||
public string Title => "Player Log";
|
||||
|
||||
public string Icon => "list";
|
||||
|
||||
public string Section => "Server";
|
||||
|
||||
public void Build(AdminPanelBuilder b)
|
||||
{
|
||||
var recent = _store.GetAll().Take(50).ToList();
|
||||
|
||||
b.RegisterTable(t =>
|
||||
{
|
||||
t.Columns = new() { "Time", "Type", "Player", "Friend Code", "Game", "Detail" };
|
||||
foreach (var e in recent)
|
||||
{
|
||||
t.Rows.Add(new()
|
||||
{
|
||||
new() { Text = e.Time.ToString("MM-dd HH:mm:ss") },
|
||||
new() { Text = e.Type },
|
||||
new() { Text = e.PlayerName ?? "—" },
|
||||
new() { Text = e.FriendCode ?? "—" },
|
||||
new() { Text = e.GameCode ?? "—" },
|
||||
new() { Text = e.Detail ?? "—" },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
b.RegisterText(txt =>
|
||||
{
|
||||
txt.Content = $"Total entries: {_store.GetAll().Count}";
|
||||
txt.Tone = "muted";
|
||||
});
|
||||
|
||||
b.RegisterButton(btn =>
|
||||
{
|
||||
btn.Label = "Export as JSON";
|
||||
btn.Icon = "download";
|
||||
btn.Style = "secondary";
|
||||
btn.OnClick(async ctx =>
|
||||
{
|
||||
var data = _store.ExportJson();
|
||||
return AdminActionResult.Ok($"Exported {data.Length} bytes. Download via /api/admin/player/logs/export");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterButton(btn =>
|
||||
{
|
||||
btn.Label = "Clear All Logs";
|
||||
btn.Icon = "trash";
|
||||
btn.Style = "danger";
|
||||
btn.OnClick(async ctx =>
|
||||
{
|
||||
_store.Clear();
|
||||
return AdminActionResult.Ok("All player logs cleared.");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,8 +2,9 @@ using Empostor.Api.Events;
|
||||
using Empostor.Api.Events.Client;
|
||||
using Empostor.Api.Events.Meeting;
|
||||
using Empostor.Api.Events.Player;
|
||||
using Empostor.Api.Innersloth;
|
||||
|
||||
namespace Empostor.Server.Service.Stat;
|
||||
namespace Empostor.Plugins.PlayerLog;
|
||||
|
||||
internal sealed class PlayerLogListener : IEventListener
|
||||
{
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.PlayerLog;
|
||||
|
||||
[EmpostorPlugin("gg.empostor.playerlog")]
|
||||
public sealed class PlayerLogPlugin : PluginBase
|
||||
{
|
||||
private readonly ILogger<PlayerLogPlugin> _logger;
|
||||
|
||||
public PlayerLogPlugin(ILogger<PlayerLogPlugin> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override ValueTask EnableAsync()
|
||||
{
|
||||
_logger.LogInformation("[PlayerLog] Enabled. Player activity logs are available in the admin panel.");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override ValueTask DisableAsync() => default;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Empostor.Api.Admin;
|
||||
using Empostor.Api.Events;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Empostor.Plugins.PlayerLog;
|
||||
|
||||
public sealed class PlayerLogPluginStartup : IPluginStartup
|
||||
{
|
||||
public void ConfigureHost(IHostBuilder host) { }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<PlayerLogStore>();
|
||||
services.AddSingleton<IEventListener, PlayerLogListener>();
|
||||
services.AddSingleton<IAdminExtension, PlayerLogAdminExtension>();
|
||||
}
|
||||
}
|
||||
+1
-15
@@ -8,7 +8,7 @@ using System.Threading;
|
||||
using Empostor.Api.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Stat;
|
||||
namespace Empostor.Plugins.PlayerLog;
|
||||
|
||||
public sealed class PlayerLogEntry
|
||||
{
|
||||
@@ -77,32 +77,18 @@ public sealed class PlayerLogStore : JsonDataStore<List<PlayerLogEntry>>
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the most recent non-empty player name recorded for a client.
|
||||
/// Used by the admin panel so disconnected players still show their
|
||||
/// original name instead of a generic "Disconnected" label.
|
||||
/// </summary>
|
||||
public string? GetLatestName(int clientId) =>
|
||||
_entries.Where(e => e.ClientId == clientId)
|
||||
.OrderByDescending(e => e.Time)
|
||||
.Select(e => e.PlayerName)
|
||||
.FirstOrDefault(n => !string.IsNullOrEmpty(n));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the most recent non-empty friend code recorded for a client.
|
||||
/// </summary>
|
||||
public string? GetLatestFriendCode(int clientId) =>
|
||||
_entries.Where(e => e.ClientId == clientId)
|
||||
.OrderByDescending(e => e.Time)
|
||||
.Select(e => e.FriendCode)
|
||||
.FirstOrDefault(f => !string.IsNullOrEmpty(f));
|
||||
|
||||
/// <summary>
|
||||
/// Removes logged entries and persists the store.
|
||||
/// When <paramref name="olderThan" /> is set, only entries strictly older
|
||||
/// than the cutoff are removed (recent entries are kept); otherwise all
|
||||
/// entries are removed.
|
||||
/// </summary>
|
||||
public void Clear(DateTime? olderThan = null)
|
||||
{
|
||||
if (olderThan.HasValue)
|
||||
@@ -0,0 +1,495 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"BouncyCastle.Cryptography": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Features": "2.2.0",
|
||||
"System.Text.Encodings.Web": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Features": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.CommandLine": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "McP+Lz/EKwvtCv48z0YImw+L1gi1gy5rHhNaNIY2CrjloV+XY8gydT8DjMR6zWeL13AFK+DioVpppwAuO1Gi1w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "C2wqUoh9OmRL1akaCcKSTmRU8z0kckfImG7zLNI8uyi47Lp+zd5LWAD17waPQEqCz3ioWOCrFUo+JJuoeZLOBw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"System.Text.Json": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ihDHu2dJYQird9pl2CbdwuNDfvCZdOS0S7SPlNfhPt0B81UTT+yyZKz2pimFZGUp3AfuBRnqUCxB2SjsZKHVUw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Json": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.6",
|
||||
"contentHash": "0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "NSmDw3K0ozNDgShSIpsZcbFIzBX4w28nDag+TfaQujkXGazBm+lid5onlWoCBy4VsLxqnnKjEBbGSJVWJMf43g==",
|
||||
"dependencies": {
|
||||
"System.Text.Encodings.Web": "8.0.0",
|
||||
"System.Text.Json": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"System.Diagnostics.DiagnosticSource": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Physical": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileSystemGlobbing": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileSystemGlobbing": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ=="
|
||||
},
|
||||
"Microsoft.Extensions.Hosting": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ItYHpdqVp5/oFLT5QqbopnkKlyFG9EW/9nhM6/yfObeKt6Su0wkBio6AizgRHGNwhJuAtlE5VIjow5JOTrip6w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.CommandLine": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Json": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "8.0.0",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Console": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Debug": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.EventLog": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.EventSource": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ixXXV0G/12g6MXK65TLngYN9V5hQQRuV+fZi882WIoVJT7h5JvoYoxTEwCgdqwLjSneqh1O+66gM8sMr9z/rsQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Console": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "e+48o7DztoYog+PY430lPxrM4mm3PbA6qucvQtUDDwVo4MO+ejMw7YGc/o2rnxbxj4isPxdfKFzTxvXMwAz83A==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Configuration": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"System.Text.Json": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Debug": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dt0x21qBdudHLW/bjMJpkixv858RRr8eSomgVbU8qljOyfrfDGi1JQvpF9w8S7ziRPtRKisuWaOwFxJM82GxeA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.EventLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3X9D3sl7EmOu7vQp5MJrmIJBl5XSdOhZPYXUeFfYa6Nnm9+tok8x3t3IVPLhm7UJtPOU61ohFchw8rNm9tIYOQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"System.Diagnostics.EventLog": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.EventSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "oKcPMrw+luz2DUAKhwFXrmFikZWnyc8l2RKoQwqU3KIZZjcfoJE0zRHAnqATfhRZhtcbjl/QkiY2Xjxp0xu+6w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0",
|
||||
"System.Text.Json": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
|
||||
},
|
||||
"Microsoft.IdentityModel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.6.2",
|
||||
"contentHash": "ULeyJwfYTMHOAArrBZorjPyM/BL5PFfwRzDtxlOxawO9vB/wVmHmbzZnOyHCOLJjel7XiVNmVnAs3H0jh4/9jQ=="
|
||||
},
|
||||
"Microsoft.IdentityModel.JsonWebTokens": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.6.2",
|
||||
"contentHash": "ySbY72MsWw7BnSZw0fCsmp/oMou8ntJkfuZUBcplV3ncXMzxcXCn3fK6Gg2YLnTT6F2G9JXl9zc2lwfVfGOWEA==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Tokens": "7.6.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.6.2",
|
||||
"contentHash": "0brV311MYxGz7Numa+pbVsxbz5tfe2nbAig1b5tQb3h/L1y5lkoPyOgD0qAfI0iX1njbwr8l9NdxIT1cDbzWKA==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Abstractions": "7.6.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.Tokens": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.6.2",
|
||||
"contentHash": "pLnhCq9UNKWkn83zutkObYuzA+sOzx6VZpPI8hB8gD/vAXVt14D0SJ0sKPftwufvAbYGSNRda1vw/IFLbkjxNg==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Logging": "7.6.2"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
},
|
||||
"Serilog.Extensions.Hosting": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "db0OcbWeSCvYQkHWu6n0v40N4kKaTAXNjlM3BKvcbwvNzYphQFcBR+36eQ/7hMMwOkJvAyLC2a9/jNdUL5NjtQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Serilog": "3.1.1",
|
||||
"Serilog.Extensions.Logging": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Extensions.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging": "8.0.0",
|
||||
"Serilog": "3.1.1"
|
||||
}
|
||||
},
|
||||
"Serilog.Settings.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "nR0iL5HwKj5v6ULo3/zpP8NMcq9E2pxYA6XKTSWCbugVs4YqPyvaqaKOY+OMpPivKp7zMEpax2UKHnDodbRB0Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyModel": "8.0.0",
|
||||
"Serilog": "3.1.1"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.Console": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.1",
|
||||
"contentHash": "6Jt8jl9y2ey8VV7nVEUAyjjyxjAQuvd5+qj4XYAT9CwcsvR70HHULGBeD+K2WCALFXf7CFsNQT4lON6qXcu2AA==",
|
||||
"dependencies": {
|
||||
"Serilog": "3.1.1"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.File": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
|
||||
},
|
||||
"System.Diagnostics.EventLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A=="
|
||||
},
|
||||
"System.IdentityModel.Tokens.Jwt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.6.2",
|
||||
"contentHash": "dyvPIzxohovbECESKj9ep7yShujU5oN9dKYR3ivvoTM7nLANNrBC5jN9ysZfY5Hp/Xs0R1b79IsdHuu6D4xwow==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.JsonWebTokens": "7.6.2",
|
||||
"Microsoft.IdentityModel.Tokens": "7.6.2"
|
||||
}
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==",
|
||||
"dependencies": {
|
||||
"System.Text.Encodings.Web": "8.0.0"
|
||||
}
|
||||
},
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )"
|
||||
}
|
||||
},
|
||||
"empostor.api.innersloth.generator": {
|
||||
"type": "Project"
|
||||
},
|
||||
"empostor.server": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Empostor.Api": "[2.0.0-dev, )",
|
||||
"Empostor.Api.Innersloth.Generator": "[2.0.0-dev, )",
|
||||
"Microsoft.Extensions.FileSystemGlobbing": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Hosting": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Http": "[8.0.0, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog.Extensions.Hosting": "[8.0.0, )",
|
||||
"Serilog.Settings.Configuration": "[8.0.0, )",
|
||||
"Serilog.Sinks.Console": "[5.0.1, )",
|
||||
"Serilog.Sinks.File": "[6.0.0, )",
|
||||
"System.IdentityModel.Tokens.Jwt": "[7.6.2, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>Empostor.Plugins.Privacy</AssemblyName>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyToMarketplace" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="..\..\marketplace" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Text.Json;
|
||||
using Empostor.Api.Admin;
|
||||
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
public sealed class PrivacyAdminExtension : IAdminExtension
|
||||
{
|
||||
private readonly PrivacyStore _store;
|
||||
|
||||
public PrivacyAdminExtension(PrivacyStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
public string Id => "privacy-policy";
|
||||
|
||||
public string Title => "Privacy Policy";
|
||||
|
||||
public string Icon => "shield";
|
||||
|
||||
public string Section => "Server";
|
||||
|
||||
public void Build(AdminPanelBuilder b)
|
||||
{
|
||||
b.RegisterTextbox(t =>
|
||||
{
|
||||
t.Label = "Privacy Policy HTML";
|
||||
t.Value = _store.GetContent();
|
||||
t.Multiline = true;
|
||||
t.SubmitOnChange = true;
|
||||
t.OnSubmit(async ctx =>
|
||||
{
|
||||
_store.SaveContent(ctx.Value ?? string.Empty);
|
||||
return AdminActionResult.Ok("Privacy policy saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterText(txt =>
|
||||
{
|
||||
txt.Content = "The privacy policy is served at /privacy. Edit the HTML above to update it.";
|
||||
txt.Tone = "muted";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
[ApiController]
|
||||
[Route("/privacy")]
|
||||
public sealed class PrivacyController : ControllerBase
|
||||
{
|
||||
private readonly PrivacyStore _store;
|
||||
private readonly ILogger<PrivacyController> _logger;
|
||||
|
||||
public PrivacyController(PrivacyStore store, ILogger<PrivacyController> logger)
|
||||
{
|
||||
_store = store;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetPrivacy()
|
||||
{
|
||||
var html = _store.GetContent();
|
||||
return Content(html, "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/admin/api/privacy")]
|
||||
public async Task<IActionResult> UpdatePrivacy()
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(body);
|
||||
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
|
||||
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
|
||||
|
||||
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
|
||||
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
|
||||
?? "empostor";
|
||||
|
||||
if (token != adminToken)
|
||||
{
|
||||
return Unauthorized(new { error = "Invalid token." });
|
||||
}
|
||||
|
||||
_store.SaveContent(content);
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
[EmpostorPlugin("gg.empostor.privacy")]
|
||||
public sealed class PrivacyPlugin : PluginBase
|
||||
{
|
||||
private readonly ILogger<PrivacyPlugin> _logger;
|
||||
|
||||
public PrivacyPlugin(ILogger<PrivacyPlugin> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override ValueTask EnableAsync()
|
||||
{
|
||||
_logger.LogInformation("[Privacy] Enabled. Privacy policy page at /privacy");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override ValueTask DisableAsync() => default;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Empostor.Api.Admin;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
public sealed class PrivacyPluginStartup : IPluginStartup, IPluginHttpStartup
|
||||
{
|
||||
public void ConfigureHost(IHostBuilder host) { }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<PrivacyStore>();
|
||||
services.AddSingleton<IAdminExtension, PrivacyAdminExtension>();
|
||||
services.AddControllers()
|
||||
.AddApplicationPart(typeof(PrivacyPluginStartup).Assembly);
|
||||
}
|
||||
|
||||
public void ConfigureWebApplication(IApplicationBuilder app)
|
||||
{
|
||||
}
|
||||
}
|
||||
+25
-53
@@ -1,70 +1,42 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Empostor.Api.Admin;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Http;
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
[ApiController]
|
||||
[Route("/privacy")]
|
||||
public sealed class PrivacyController : ControllerBase
|
||||
public sealed class PrivacyStore
|
||||
{
|
||||
private static readonly string PagesDir = Path.Combine(Directory.GetCurrentDirectory(), "Pages");
|
||||
private static readonly string PrivacyFile = Path.Combine(PagesDir, "privacy.html");
|
||||
private static bool _initialized;
|
||||
private static readonly object _initLock = new();
|
||||
|
||||
private readonly ILogger<PrivacyController> _logger;
|
||||
private readonly ILogger<PrivacyStore> _logger;
|
||||
private bool _initialized;
|
||||
private readonly object _initLock = new();
|
||||
|
||||
public PrivacyController(ILogger<PrivacyController> logger)
|
||||
public PrivacyStore(ILogger<PrivacyStore> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GetContent()
|
||||
{
|
||||
EnsureFile();
|
||||
if (!File.Exists(PrivacyFile))
|
||||
{
|
||||
return DefaultPrivacyHtml();
|
||||
}
|
||||
|
||||
return File.ReadAllText(PrivacyFile);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetPrivacy()
|
||||
public void SaveContent(string content)
|
||||
{
|
||||
if (!System.IO.File.Exists(PrivacyFile))
|
||||
{
|
||||
return Content(DefaultPrivacyHtml(), "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
var html = System.IO.File.ReadAllText(PrivacyFile);
|
||||
return Content(html, "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/admin/api/privacy")]
|
||||
public async Task<IActionResult> UpdatePrivacy()
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var doc = System.Text.Json.JsonDocument.Parse(body);
|
||||
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
|
||||
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
|
||||
|
||||
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
|
||||
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
|
||||
?? "empostor";
|
||||
|
||||
if (token != adminToken)
|
||||
{
|
||||
return Unauthorized(new { error = "Invalid token." });
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllText(PrivacyFile, content);
|
||||
_logger.LogInformation("PrivacyControllerPrivacy policy updated.");
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
EnsureFile();
|
||||
File.WriteAllText(PrivacyFile, content);
|
||||
_logger.LogInformation("Privacy policy updated.");
|
||||
}
|
||||
|
||||
private void EnsureFile()
|
||||
@@ -88,10 +60,10 @@ public sealed class PrivacyController : ControllerBase
|
||||
Directory.CreateDirectory(PagesDir);
|
||||
}
|
||||
|
||||
if (!System.IO.File.Exists(PrivacyFile))
|
||||
if (!File.Exists(PrivacyFile))
|
||||
{
|
||||
System.IO.File.WriteAllText(PrivacyFile, DefaultPrivacyHtml());
|
||||
_logger.LogInformation("PrivacyControllerWritten default Pages/privacy.html");
|
||||
File.WriteAllText(PrivacyFile, DefaultPrivacyHtml());
|
||||
_logger.LogInformation("Written default Pages/privacy.html");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"BouncyCastle.Cryptography": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Features": "2.2.0",
|
||||
"System.Text.Encodings.Web": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Features": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.6",
|
||||
"contentHash": "0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"System.Diagnostics.DiagnosticSource": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
|
||||
},
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Commands;
|
||||
using Empostor.Api.Net;
|
||||
using Empostor.Server.Service.Admin.Ban;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Next.Hazel;
|
||||
|
||||
namespace Empostor.Server.Commands.Commands;
|
||||
@@ -64,14 +64,14 @@ public sealed class BanCommand : ICommand
|
||||
// Ban by friend code if available
|
||||
if (!string.IsNullOrEmpty(fc))
|
||||
{
|
||||
_banStore.BanFriendCode(fc, reason);
|
||||
_banStore.BanFriendCode(fc, reason, null);
|
||||
}
|
||||
|
||||
// Ban by IP
|
||||
var ip = target.Client.Connection?.EndPoint?.Address;
|
||||
if (ip != null)
|
||||
{
|
||||
_banStore.BanIp(ip, reason);
|
||||
_banStore.BanIp(ip, reason, null);
|
||||
}
|
||||
|
||||
await target.Client.DisconnectAsync(DisconnectReason.Banned, reason);
|
||||
|
||||
@@ -16,11 +16,8 @@ using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Net;
|
||||
using Empostor.Api.Net.Manager;
|
||||
using Empostor.Server.Http.Admin;
|
||||
using Empostor.Server.Service.Admin.Ban;
|
||||
using Empostor.Server.Service.Admin.Reactor;
|
||||
using Empostor.Server.Service.Admin.Report;
|
||||
using Empostor.Server.Service.Api;
|
||||
using Empostor.Server.Service.Stat;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Empostor.Server.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -45,7 +42,6 @@ namespace Empostor.Server.Http
|
||||
private readonly BanStore _bans;
|
||||
private readonly AdminConfig _config;
|
||||
private readonly ReportStore _reportStore;
|
||||
private readonly PlayerLogStore _playerLogs;
|
||||
private readonly HplpStore _hplpStore;
|
||||
private readonly IOptions<ServerConfig> _serverConfig;
|
||||
private readonly IOptions<HttpServerConfig> _httpServerConfig;
|
||||
@@ -62,7 +58,6 @@ namespace Empostor.Server.Http
|
||||
BanStore bans,
|
||||
IOptions<AdminConfig> config,
|
||||
ReportStore reportStore,
|
||||
PlayerLogStore playerLogs,
|
||||
HplpStore hplpStore,
|
||||
IOptions<ServerConfig> serverConfig,
|
||||
IOptions<HttpServerConfig> httpServerConfig,
|
||||
@@ -76,7 +71,6 @@ namespace Empostor.Server.Http
|
||||
_bans = bans;
|
||||
_config = config.Value;
|
||||
_reportStore = reportStore;
|
||||
_playerLogs = playerLogs;
|
||||
_hplpStore = hplpStore;
|
||||
_serverConfig = serverConfig;
|
||||
_httpServerConfig = httpServerConfig;
|
||||
@@ -505,7 +499,7 @@ namespace Empostor.Server.Http
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new { banned = entry.Value, disconnected = kicked });
|
||||
return Ok(new { banned = entry, disconnected = kicked });
|
||||
}
|
||||
|
||||
[HttpPost("/api/admin/ban/fc")]
|
||||
@@ -538,7 +532,7 @@ namespace Empostor.Server.Http
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new { banned = entry.Value, disconnected = kicked });
|
||||
return Ok(new { banned = entry, disconnected = kicked });
|
||||
}
|
||||
|
||||
[HttpPost("/api/admin/unban/ip")]
|
||||
@@ -549,7 +543,12 @@ namespace Empostor.Server.Http
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok(new { removed = _bans.UnbanIp(req.Value) });
|
||||
if (!IPAddress.TryParse(req.Value, out var ip))
|
||||
{
|
||||
return BadRequest(Err("Invalid IP"));
|
||||
}
|
||||
|
||||
return Ok(new { removed = _bans.UnbanIp(ip) });
|
||||
}
|
||||
|
||||
[HttpPost("/api/admin/unban/fc")]
|
||||
@@ -626,111 +625,6 @@ namespace Empostor.Server.Http
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("/api/admin/player/logs/clients")]
|
||||
public IActionResult GetPlayerLogClients()
|
||||
{
|
||||
if (!IsAuthenticated())
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok(_playerLogs.GetLoggedClientIds().Select(id => new
|
||||
{
|
||||
clientId = id,
|
||||
name = FindClient(id)?.Name ?? _playerLogs.GetLatestName(id) ?? "Disconnected",
|
||||
friendCode = FindClient(id)?.FriendCode ?? _playerLogs.GetLatestFriendCode(id) ?? "—",
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpPost("/api/admin/player/logs/clear")]
|
||||
public IActionResult ClearPlayerLogs([FromQuery] string? olderThan)
|
||||
{
|
||||
if (!IsAuthenticated())
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
DateTime? cutoff = null;
|
||||
if (!string.IsNullOrEmpty(olderThan)
|
||||
&& !olderThan.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cutoff = ParseLogCutoff(olderThan);
|
||||
if (cutoff == null)
|
||||
{
|
||||
return BadRequest(new { error = "Invalid time range." });
|
||||
}
|
||||
}
|
||||
|
||||
_playerLogs.Clear(cutoff);
|
||||
_logger.LogInformation("Admin cleared player logs (olderThan: {OlderThan})", olderThan ?? "all");
|
||||
return Ok(new { cleared = true });
|
||||
}
|
||||
|
||||
private static DateTime? ParseLogCutoff(string value)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
switch (value.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "1h":
|
||||
return now.AddHours(-1);
|
||||
case "24h":
|
||||
return now.AddDays(-1);
|
||||
case "7d":
|
||||
return now.AddDays(-7);
|
||||
case "30d":
|
||||
return now.AddDays(-30);
|
||||
default:
|
||||
return DateTime.TryParse(
|
||||
value,
|
||||
null,
|
||||
System.Globalization.DateTimeStyles.AssumeUniversal
|
||||
| System.Globalization.DateTimeStyles.AdjustToUniversal,
|
||||
out var dt)
|
||||
? dt.ToUniversalTime()
|
||||
: (DateTime?)null;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("/api/admin/player/logs")]
|
||||
public IActionResult GetPlayerLogs([FromQuery] int? clientId)
|
||||
{
|
||||
if (!IsAuthenticated())
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var logs = clientId.HasValue
|
||||
? _playerLogs.GetByClient(clientId.Value)
|
||||
: _playerLogs.GetAll();
|
||||
return Ok(logs.Select(e => new
|
||||
{
|
||||
time = e.Time.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
type = e.Type,
|
||||
clientId = e.ClientId,
|
||||
playerName = e.PlayerName ?? "—",
|
||||
friendCode = e.FriendCode ?? "—",
|
||||
gameCode = e.GameCode ?? "—",
|
||||
detail = e.Detail ?? "—",
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("/api/admin/player/logs/export")]
|
||||
public IActionResult ExportPlayerLogs([FromQuery] int? clientId)
|
||||
{
|
||||
if (!IsAuthenticated())
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var data = clientId.HasValue
|
||||
? _playerLogs.ExportJson(clientId.Value)
|
||||
: _playerLogs.ExportJson();
|
||||
var name = clientId.HasValue
|
||||
? $"player_{clientId}_logs.json"
|
||||
: "all_player_logs.json";
|
||||
return File(data, "application/json; charset=utf-8", name);
|
||||
}
|
||||
|
||||
[HttpGet("/api/admin/hplp")]
|
||||
public IActionResult GetHplp()
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Empostor.Api.Config;
|
||||
using Empostor.Api.Games;
|
||||
using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Innersloth;
|
||||
using Empostor.Server.Service.Admin.Reactor;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Empostor.Server.Service.Api;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -13,7 +13,7 @@ using Empostor.Api.Net.Messages.C2S;
|
||||
using Empostor.Api.Net.Messages.S2C;
|
||||
using Empostor.Server.Events.Player;
|
||||
using Empostor.Server.Net.Manager;
|
||||
using Empostor.Server.Service.Admin.Report;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Next.Hazel;
|
||||
|
||||
@@ -14,9 +14,10 @@ using Empostor.Api.Service;
|
||||
using Empostor.Server.Events.Client;
|
||||
using Empostor.Server.Net.Factories;
|
||||
using Empostor.Server.Service;
|
||||
using Empostor.Server.Service.Admin.Reactor;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Empostor.Server.Service.Auth;
|
||||
using Empostor.Server.Utils;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Next.Hazel;
|
||||
|
||||
@@ -6,7 +6,7 @@ using Empostor.Api.Games;
|
||||
using Empostor.Api.Net;
|
||||
using Next.Hazel;
|
||||
using Empostor.Server.Events;
|
||||
using Empostor.Server.Service.Admin.Reactor;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Empostor.Server.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -25,14 +25,11 @@ using Empostor.Server.Net.Manager;
|
||||
using Empostor.Server.Net.Messages;
|
||||
using Empostor.Server.Plugins;
|
||||
using Empostor.Server.Recorder;
|
||||
using Empostor.Server.Service.Admin.Ban;
|
||||
using Empostor.Server.Service.Admin.Reactor;
|
||||
using Empostor.Server.Service.Admin.Report;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Empostor.Server.Service.Api;
|
||||
using Empostor.Api.Service;
|
||||
using Empostor.Server.Service.Auth;
|
||||
using Empostor.Server.Service.Firewall;
|
||||
using Empostor.Server.Service.Stat;
|
||||
using Empostor.Server.Utils;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -199,8 +196,6 @@ namespace Empostor.Server
|
||||
services.AddSingleton<ReportStore>();
|
||||
services.AddSingleton<IEventListener, BanEnforcementListener>();
|
||||
services.AddSingleton<IEventListener, ReactorHandshakeListener>();
|
||||
services.AddSingleton<PlayerLogStore>();
|
||||
services.AddSingleton<IEventListener, PlayerLogListener>();
|
||||
services.AddSingleton<BanCommand>();
|
||||
services.AddSingleton<HplpStore>();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Empostor.Api.Net.Messages;
|
||||
using Empostor.Server.Net;
|
||||
using Empostor.Server.Net.Hazel;
|
||||
using Empostor.Server.Net.Manager;
|
||||
using Empostor.Server.Service.Admin.Report;
|
||||
using Empostor.Server.Service.Shared;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Empostor.Api.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Ban
|
||||
{
|
||||
public sealed class BanStore : JsonDataStore<BanStore.BanData>
|
||||
{
|
||||
private ConcurrentDictionary<string, BanEntry> _ips = new();
|
||||
private ConcurrentDictionary<string, BanEntry> _friendCodes = new();
|
||||
|
||||
public BanStore(ILogger<BanStore> logger)
|
||||
: base(logger, legacyPath: "bans.json")
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
public bool IsIpBanned(IPAddress ip) => GetIpBan(ip) != null;
|
||||
|
||||
public bool IsFriendCodeBanned(string? fc) => GetFriendCodeBan(fc) != null;
|
||||
|
||||
public BanEntry? GetIpBan(IPAddress ip)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
if (!_ips.TryGetValue(key, out var entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.IsExpired)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
_ips.TryRemove(key, out _);
|
||||
SaveFireAndForget();
|
||||
return null;
|
||||
}
|
||||
|
||||
public BanEntry? GetFriendCodeBan(string? fc)
|
||||
{
|
||||
if (fc == null || !_friendCodes.TryGetValue(fc, out var entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.IsExpired)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
_friendCodes.TryRemove(fc, out _);
|
||||
SaveFireAndForget();
|
||||
return null;
|
||||
}
|
||||
|
||||
public BanEntry BanIp(IPAddress ip, string reason, DateTime? bannedUntil = null)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
var entry = new BanEntry { Value = key, Reason = reason, BannedAt = DateTime.UtcNow, BannedUntil = bannedUntil };
|
||||
_ips[key] = entry;
|
||||
SaveFireAndForget();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public bool UnbanIp(string key)
|
||||
{
|
||||
var r = _ips.TryRemove(key, out _);
|
||||
if (r)
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public BanEntry BanFriendCode(string fc, string reason, DateTime? bannedUntil = null)
|
||||
{
|
||||
var entry = new BanEntry { Value = fc, Reason = reason, BannedAt = DateTime.UtcNow, BannedUntil = bannedUntil };
|
||||
_friendCodes[fc] = entry;
|
||||
SaveFireAndForget();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public bool UnbanFriendCode(string key)
|
||||
{
|
||||
var r = _friendCodes.TryRemove(key, out _);
|
||||
if (r)
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public IReadOnlyList<BanEntry> AllIpBans()
|
||||
{
|
||||
if (PruneExpired())
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return _ips.Values.OrderByDescending(b => b.BannedAt).ToList();
|
||||
}
|
||||
|
||||
public IReadOnlyList<BanEntry> AllFriendCodeBans()
|
||||
{
|
||||
if (PruneExpired())
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return _friendCodes.Values.OrderByDescending(b => b.BannedAt).ToList();
|
||||
}
|
||||
|
||||
public (int IpCount, int FcCount) Stats()
|
||||
{
|
||||
if (PruneExpired())
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return (_ips.Count, _friendCodes.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ban duration string into a UTC expiry timestamp.
|
||||
/// Supported formats: "permanent"/"forever" (or empty) for a permanent ban,
|
||||
/// or "<number><unit>" where unit is h (hours), d (days), mo (months) or y (years).
|
||||
/// Returns null for a permanent ban.
|
||||
/// </summary>
|
||||
public static DateTime? ParseDuration(string? duration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(duration))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = duration.Trim();
|
||||
if (value.Equals("permanent", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("forever", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("perm", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = Regex.Match(value, @"^(\d+)(h|d|mo|y)$", RegexOptions.IgnoreCase);
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var amount = int.Parse(match.Groups[1].Value);
|
||||
var unit = match.Groups[2].Value.ToLowerInvariant();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return unit switch
|
||||
{
|
||||
"h" => now.AddHours(amount),
|
||||
"d" => now.AddDays(amount),
|
||||
"mo" => now.AddMonths(amount),
|
||||
"y" => now.AddYears(amount),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
protected override BanData GetSnapshot()
|
||||
{
|
||||
PruneExpired();
|
||||
return new()
|
||||
{
|
||||
Ips = new(_ips),
|
||||
FriendCodes = new(_friendCodes),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void ApplySnapshot(BanData data)
|
||||
{
|
||||
_ips = new(data.Ips ?? new());
|
||||
_friendCodes = new(data.FriendCodes ?? new());
|
||||
}
|
||||
|
||||
private bool PruneExpired()
|
||||
{
|
||||
var changed = false;
|
||||
foreach (var (key, entry) in _ips)
|
||||
{
|
||||
if (entry.IsExpired && _ips.TryRemove(key, out _))
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, entry) in _friendCodes)
|
||||
{
|
||||
if (entry.IsExpired && _friendCodes.TryRemove(key, out _))
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static string Normalize(IPAddress ip)
|
||||
=> ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
|
||||
|
||||
public sealed class BanData
|
||||
{
|
||||
[JsonPropertyName("ips")]
|
||||
public Dictionary<string, BanEntry>? Ips { get; set; }
|
||||
|
||||
[JsonPropertyName("friendCodes")]
|
||||
public Dictionary<string, BanEntry>? FriendCodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BanEntry
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public required string Value { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public required string Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("bannedAt")]
|
||||
public required DateTime BannedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("bannedUntil")]
|
||||
public DateTime? BannedUntil { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsExpired => BannedUntil.HasValue && BannedUntil.Value <= DateTime.UtcNow;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsPermanent => !BannedUntil.HasValue;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Empostor.Api.Service;
|
||||
using Empostor.Server.Service.Stat;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service;
|
||||
@@ -16,14 +15,17 @@ public sealed class ClientIdStore : JsonDataStore<long>
|
||||
{
|
||||
private long _lastId;
|
||||
|
||||
public ClientIdStore(ILogger<ClientIdStore> logger, PlayerLogStore playerLogs)
|
||||
public ClientIdStore(ILogger<ClientIdStore> logger)
|
||||
: base(logger)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
_lastId = System.Math.Max(_lastId, playerLogs.GetMaxClientId());
|
||||
if (_lastId > 0)
|
||||
public void InitializeWithMaxId(long maxId)
|
||||
{
|
||||
if (maxId > _lastId)
|
||||
{
|
||||
_lastId = maxId;
|
||||
SaveFireAndForget();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ using Empostor.Api.Events.Client;
|
||||
using Empostor.Api.Languages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Ban;
|
||||
namespace Empostor.Server.Service.Shared;
|
||||
|
||||
public sealed class BanEnforcementListener : IEventListener
|
||||
{
|
||||
@@ -26,7 +26,7 @@ public sealed class BanEnforcementListener : IEventListener
|
||||
var client = e.Client;
|
||||
var ip = client.Connection?.EndPoint?.Address;
|
||||
|
||||
// IP 封禁检查
|
||||
// IP ban check
|
||||
if (ip != null && _bans.GetIpBan(ip) is { } ipBan)
|
||||
{
|
||||
_logger.LogWarning("BanRejecting banned IP {Ip} ({Name})", ip, client.Name);
|
||||
@@ -34,7 +34,7 @@ public sealed class BanEnforcementListener : IEventListener
|
||||
return;
|
||||
}
|
||||
|
||||
// FriendCode 封禁检查
|
||||
// FriendCode ban check
|
||||
if (_bans.GetFriendCodeBan(client.FriendCode) is { } fcBan)
|
||||
{
|
||||
_logger.LogWarning("BanRejecting banned FriendCode {FC} ({Name})", client.FriendCode, client.Name);
|
||||
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Empostor.Api.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Shared
|
||||
{
|
||||
public sealed class BanStore : JsonDataStore<BanStore.BanData>
|
||||
{
|
||||
private ConcurrentDictionary<string, BanEntry> _ips = new();
|
||||
private ConcurrentDictionary<string, BanEntry> _friendCodes = new();
|
||||
|
||||
public BanStore(ILogger<BanStore> logger)
|
||||
: base(logger, legacyPath: "bans.json")
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
public bool IsIpBanned(IPAddress ip) => GetIpBan(ip) != null;
|
||||
|
||||
public bool IsFriendCodeBanned(string? fc) => GetFriendCodeBan(fc) != null;
|
||||
|
||||
public BanEntry? GetIpBan(IPAddress ip)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
if (!_ips.TryGetValue(key, out var entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.IsExpired)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
_ips.TryRemove(key, out _);
|
||||
SaveFireAndForget();
|
||||
return null;
|
||||
}
|
||||
|
||||
public BanEntry? GetFriendCodeBan(string? fc)
|
||||
{
|
||||
if (fc == null || !_friendCodes.TryGetValue(fc, out var entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.IsExpired)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
_friendCodes.TryRemove(fc, out _);
|
||||
SaveFireAndForget();
|
||||
return null;
|
||||
}
|
||||
|
||||
public BanEntry BanIp(IPAddress ip, string reason, DateTime? expiresAt)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
var entry = new BanEntry { Reason = reason, BannedAt = DateTime.UtcNow, BannedUntil = expiresAt };
|
||||
_ips[key] = entry;
|
||||
SaveFireAndForget();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public BanEntry BanFriendCode(string fc, string reason, DateTime? expiresAt)
|
||||
{
|
||||
var entry = new BanEntry { Reason = reason, BannedAt = DateTime.UtcNow, BannedUntil = expiresAt };
|
||||
_friendCodes[fc] = entry;
|
||||
SaveFireAndForget();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public bool UnbanIp(IPAddress ip)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
var removed = _ips.TryRemove(key, out _);
|
||||
if (removed)
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public bool UnbanFriendCode(string fc)
|
||||
{
|
||||
var removed = _friendCodes.TryRemove(fc, out _);
|
||||
if (removed)
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public (int IpCount, int FriendCodeCount) Stats()
|
||||
{
|
||||
CleanExpired();
|
||||
return (_ips.Count, _friendCodes.Count);
|
||||
}
|
||||
|
||||
public List<BanEntry> AllIpBans()
|
||||
{
|
||||
CleanExpired();
|
||||
return _ips.Values.ToList();
|
||||
}
|
||||
|
||||
public List<BanEntry> AllFriendCodeBans()
|
||||
{
|
||||
CleanExpired();
|
||||
return _friendCodes.Values.ToList();
|
||||
}
|
||||
|
||||
private void CleanExpired()
|
||||
{
|
||||
foreach (var kv in _ips)
|
||||
{
|
||||
if (kv.Value.IsExpired)
|
||||
{
|
||||
_ips.TryRemove(kv.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in _friendCodes)
|
||||
{
|
||||
if (kv.Value.IsExpired)
|
||||
{
|
||||
_friendCodes.TryRemove(kv.Key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string Normalize(IPAddress ip)
|
||||
=> ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
|
||||
|
||||
public static DateTime? ParseDuration(string? duration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(duration))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = Regex.Match(duration.Trim(), @"^(\d+)([smhd])$", RegexOptions.IgnoreCase);
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = int.Parse(match.Groups[1].Value);
|
||||
var unit = match.Groups[2].Value.ToLowerInvariant()[0];
|
||||
var span = unit switch
|
||||
{
|
||||
's' => TimeSpan.FromSeconds(value),
|
||||
'm' => TimeSpan.FromMinutes(value),
|
||||
'h' => TimeSpan.FromHours(value),
|
||||
'd' => TimeSpan.FromDays(value),
|
||||
_ => TimeSpan.Zero,
|
||||
};
|
||||
|
||||
return DateTime.UtcNow.Add(span);
|
||||
}
|
||||
|
||||
protected override BanData GetSnapshot()
|
||||
{
|
||||
CleanExpired();
|
||||
return new BanData
|
||||
{
|
||||
Ips = _ips.ToDictionary(kv => kv.Key, kv => kv.Value),
|
||||
FriendCodes = _friendCodes.ToDictionary(kv => kv.Key, kv => kv.Value),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void ApplySnapshot(BanData data)
|
||||
{
|
||||
_ips = new ConcurrentDictionary<string, BanEntry>(data.Ips ?? new Dictionary<string, BanEntry>());
|
||||
_friendCodes = new ConcurrentDictionary<string, BanEntry>(data.FriendCodes ?? new Dictionary<string, BanEntry>());
|
||||
}
|
||||
|
||||
public sealed class BanData
|
||||
{
|
||||
public Dictionary<string, BanEntry> Ips { get; set; } = new();
|
||||
|
||||
public Dictionary<string, BanEntry> FriendCodes { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class BanEntry
|
||||
{
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
|
||||
public DateTime BannedAt { get; set; }
|
||||
|
||||
public DateTime? BannedUntil { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsExpired => BannedUntil.HasValue && DateTime.UtcNow > BannedUntil.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-57
@@ -7,7 +7,7 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Utils
|
||||
namespace Empostor.Server.Service.Shared
|
||||
{
|
||||
public class IpGeolocationService
|
||||
{
|
||||
@@ -48,84 +48,48 @@ namespace Empostor.Server.Utils
|
||||
{
|
||||
var normalizedIp = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4() : ip;
|
||||
var url = string.Format(ApiUrl, normalizedIp);
|
||||
|
||||
using var client = _httpClientFactory.CreateClient("ipgeo");
|
||||
using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
var json = await client.GetStringAsync(url, cts.Token);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("status", out var status) && status.GetString() == "success")
|
||||
var response = await client.GetStringAsync(url);
|
||||
using var doc = JsonDocument.Parse(response);
|
||||
var status = doc.RootElement.GetProperty("status").GetString();
|
||||
if (status != "success")
|
||||
{
|
||||
var country = root.TryGetProperty("country", out var c) ? c.GetString() ?? string.Empty : string.Empty;
|
||||
var region = root.TryGetProperty("regionName", out var r) ? r.GetString() ?? string.Empty : string.Empty;
|
||||
var city = root.TryGetProperty("city", out var ct) ? ct.GetString() ?? string.Empty : string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(country) && string.IsNullOrEmpty(city))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var parts = country != city
|
||||
? (region != city ? $"{country} {region} {city}" : $"{country} {city}")
|
||||
: country;
|
||||
|
||||
return parts.Trim();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
var country = doc.RootElement.TryGetProperty("country", out var c) ? c.GetString() : string.Empty;
|
||||
var region = doc.RootElement.TryGetProperty("regionName", out var r) ? r.GetString() : string.Empty;
|
||||
var city = doc.RootElement.TryGetProperty("city", out var ci) ? ci.GetString() : string.Empty;
|
||||
|
||||
return $"{country} {region} {city}".Trim();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "IP geolocation lookup failed for {Ip}", ip);
|
||||
_logger.LogDebug(ex, "IP geolocation query failed for {Ip}", ip);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPrivateIp(IPAddress ip)
|
||||
{
|
||||
if (ip.IsIPv4MappedToIPv6)
|
||||
if (IPAddress.IsLoopback(ip))
|
||||
{
|
||||
ip = ip.MapToIPv4();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ip.AddressFamily != AddressFamily.InterNetwork)
|
||||
{
|
||||
return true; // Don't query non-IPv4 for now
|
||||
return true;
|
||||
}
|
||||
|
||||
var bytes = ip.GetAddressBytes();
|
||||
if (bytes.Length != 4)
|
||||
return bytes[0] switch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 127.0.0.0/8
|
||||
if (bytes[0] == 127)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 10.0.0.0/8
|
||||
if (bytes[0] == 10)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 172.16.0.0/12
|
||||
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 192.168.0.0/16
|
||||
if (bytes[0] == 192 && bytes[1] == 168)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
10 => true,
|
||||
172 => bytes[1] >= 16 && bytes[1] <= 31,
|
||||
192 => bytes[1] == 168,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Empostor.Api.Events;
|
||||
using Empostor.Api.Events.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Reactor
|
||||
namespace Empostor.Server.Service.Shared
|
||||
{
|
||||
// Based on Reactor.Empostor protocol:
|
||||
// https://github.com/NuclearPowered/Reactor.Empostor
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Empostor.Api.Net;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Reactor
|
||||
namespace Empostor.Server.Service.Shared
|
||||
{
|
||||
|
||||
public static class ReactorModExtensions
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using Empostor.Api.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Report
|
||||
namespace Empostor.Server.Service.Shared
|
||||
{
|
||||
public sealed class ReportStore : JsonDataStore<List<ReportStore.ReportEntry>>
|
||||
{
|
||||
@@ -47,6 +47,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.ChatFilter
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.PlayerStats", "Empostor.Plugins.PlayerStats\Empostor.Plugins.PlayerStats.csproj", "{2C33B35B-3A9F-4EBA-A7FA-5264E083BBB2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.PlayerLog", "Empostor.Plugins.PlayerLog\Empostor.Plugins.PlayerLog.csproj", "{135D8F7F-C330-444E-94D4-D2D3944F688A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.Monitor", "Empostor.Plugins.Monitor\Empostor.Plugins.Monitor.csproj", "{B50665B5-AEDB-4F56-B54A-542863EDB009}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.Privacy", "Empostor.Plugins.Privacy\Empostor.Plugins.Privacy.csproj", "{959879FE-2FE0-4FD8-A706-0FA1B3138F45}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -297,6 +303,42 @@ Global
|
||||
{2C33B35B-3A9F-4EBA-A7FA-5264E083BBB2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2C33B35B-3A9F-4EBA-A7FA-5264E083BBB2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2C33B35B-3A9F-4EBA-A7FA-5264E083BBB2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009}.Release|x86.Build.0 = Release|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Release|x64.Build.0 = Release|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -319,6 +361,9 @@ Global
|
||||
{70582074-88B9-419C-85ED-46536AE2D6AA} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
|
||||
{AF4C4FF8-47C8-4E89-9DD4-3BD1781E18CD} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
|
||||
{2C33B35B-3A9F-4EBA-A7FA-5264E083BBB2} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
|
||||
{135D8F7F-C330-444E-94D4-D2D3944F688A} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
|
||||
{B50665B5-AEDB-4F56-B54A-542863EDB009} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
|
||||
{959879FE-2FE0-4FD8-A706-0FA1B3138F45} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DAF5625C-7E46-4DCC-AF2C-11D20D965454}
|
||||
|
||||
Reference in new issue
Block a user