Add payload serializer guardrails
This commit is contained in:
@@ -1236,7 +1236,7 @@ public partial class Workspace : IAsyncDisposable
|
||||
private const string CampaignSessionKey = "campaign";
|
||||
private const string MobilePanelSessionKey = "play-panel";
|
||||
private const string RollVisibilitySessionKey = "roll-visibility";
|
||||
private const int CampaignLogWindowSize = 50;
|
||||
private const int CampaignLogWindowSize = 25;
|
||||
private const int ToastDurationMs = 3200;
|
||||
|
||||
private sealed record WorkspaceToast(Guid Id, string Message, bool IsError);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.JSInterop;
|
||||
using RpgRoller.Contracts;
|
||||
|
||||
namespace RpgRoller.Components;
|
||||
|
||||
@@ -37,7 +38,7 @@ public sealed class RpgRollerApiClient
|
||||
throw new ApiRequestException(response.Status, response.Error ?? "Request failed.");
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private static readonly JsonSerializerOptions JsonOptions = RpgRollerJson.CreateSerializerOptions();
|
||||
private readonly IJSRuntime m_Js;
|
||||
}
|
||||
|
||||
@@ -49,4 +50,4 @@ public sealed class ApiRequestException : Exception
|
||||
}
|
||||
|
||||
public int StatusCode { get; }
|
||||
}
|
||||
}
|
||||
|
||||
19
RpgRoller/Contracts/RpgRollerJson.cs
Normal file
19
RpgRoller/Contracts/RpgRollerJson.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RpgRoller.Contracts;
|
||||
|
||||
public static class RpgRollerJson
|
||||
{
|
||||
public static JsonSerializerOptions CreateSerializerOptions()
|
||||
{
|
||||
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||
Configure(options);
|
||||
return options;
|
||||
}
|
||||
|
||||
public static void Configure(JsonSerializerOptions options)
|
||||
{
|
||||
if (!options.TypeInfoResolverChain.Contains(RpgRollerJsonSerializerContext.Default))
|
||||
options.TypeInfoResolverChain.Insert(0, RpgRollerJsonSerializerContext.Default);
|
||||
}
|
||||
}
|
||||
58
RpgRoller/Contracts/RpgRollerJsonSerializerContext.cs
Normal file
58
RpgRoller/Contracts/RpgRollerJsonSerializerContext.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RpgRoller.Contracts;
|
||||
|
||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
|
||||
[JsonSerializable(typeof(ApiError))]
|
||||
[JsonSerializable(typeof(AdminUserSummary))]
|
||||
[JsonSerializable(typeof(AdminUserSummary[]))]
|
||||
[JsonSerializable(typeof(CampaignGmSummary))]
|
||||
[JsonSerializable(typeof(CampaignLogEntry))]
|
||||
[JsonSerializable(typeof(CampaignLogEntry[]))]
|
||||
[JsonSerializable(typeof(CampaignLogListEntry))]
|
||||
[JsonSerializable(typeof(CampaignLogListEntry[]))]
|
||||
[JsonSerializable(typeof(CampaignLogPage))]
|
||||
[JsonSerializable(typeof(CampaignOption))]
|
||||
[JsonSerializable(typeof(CampaignOption[]))]
|
||||
[JsonSerializable(typeof(CampaignRollDetail))]
|
||||
[JsonSerializable(typeof(CampaignRoster))]
|
||||
[JsonSerializable(typeof(CampaignStateSnapshot))]
|
||||
[JsonSerializable(typeof(CampaignSummary))]
|
||||
[JsonSerializable(typeof(CampaignSummary[]))]
|
||||
[JsonSerializable(typeof(CharacterSheet))]
|
||||
[JsonSerializable(typeof(CharacterSheetSkill))]
|
||||
[JsonSerializable(typeof(CharacterSheetSkillGroup))]
|
||||
[JsonSerializable(typeof(CharacterStateVersion))]
|
||||
[JsonSerializable(typeof(CharacterStateVersion[]))]
|
||||
[JsonSerializable(typeof(CharacterSummary))]
|
||||
[JsonSerializable(typeof(CharacterSummary[]))]
|
||||
[JsonSerializable(typeof(CreateCampaignRequest))]
|
||||
[JsonSerializable(typeof(CreateCharacterRequest))]
|
||||
[JsonSerializable(typeof(CreateSkillGroupRequest))]
|
||||
[JsonSerializable(typeof(CreateSkillRequest))]
|
||||
[JsonSerializable(typeof(HealthResponse))]
|
||||
[JsonSerializable(typeof(IReadOnlyList<AdminUserSummary>))]
|
||||
[JsonSerializable(typeof(IReadOnlyList<CharacterStateVersion>))]
|
||||
[JsonSerializable(typeof(IReadOnlyList<RollDieResult>))]
|
||||
[JsonSerializable(typeof(IReadOnlyList<string>))]
|
||||
[JsonSerializable(typeof(LoginRequest))]
|
||||
[JsonSerializable(typeof(MeResponse))]
|
||||
[JsonSerializable(typeof(RegisterRequest))]
|
||||
[JsonSerializable(typeof(RollDieResult))]
|
||||
[JsonSerializable(typeof(RollDieResult[]))]
|
||||
[JsonSerializable(typeof(RollResult))]
|
||||
[JsonSerializable(typeof(RollSkillRequest))]
|
||||
[JsonSerializable(typeof(RulesetDefinition))]
|
||||
[JsonSerializable(typeof(RulesetDefinition[]))]
|
||||
[JsonSerializable(typeof(SkillGroupSummary))]
|
||||
[JsonSerializable(typeof(SkillSummary))]
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
[JsonSerializable(typeof(UpdateCharacterRequest))]
|
||||
[JsonSerializable(typeof(UpdateSkillGroupRequest))]
|
||||
[JsonSerializable(typeof(UpdateSkillRequest))]
|
||||
[JsonSerializable(typeof(UpdateUserRolesRequest))]
|
||||
[JsonSerializable(typeof(UserSummary))]
|
||||
public partial class RpgRollerJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using RpgRoller.Api;
|
||||
using RpgRoller.Components;
|
||||
using RpgRoller.Contracts;
|
||||
using RpgRoller.Hosting;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddRpgRollerCore(builder.Configuration, builder.Environment);
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
builder.Services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = true;
|
||||
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/json"]);
|
||||
});
|
||||
builder.Services.ConfigureHttpJsonOptions(options => RpgRollerJson.Configure(options.SerializerOptions));
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<RpgRollerApiClient>();
|
||||
builder.Services.AddScoped<WorkspaceSessionTokenAccessor>();
|
||||
@@ -25,6 +33,7 @@ if (!string.IsNullOrWhiteSpace(configuredPathBase))
|
||||
app.UsePathBase(normalizedPathBase);
|
||||
}
|
||||
|
||||
app.UseResponseCompression();
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
|
||||
@@ -797,7 +797,7 @@ public sealed class GameService : IGameService
|
||||
|
||||
var (user, campaign) = context.Value!;
|
||||
var entries = GetVisibleCampaignLogEntriesLocked(user, campaign)
|
||||
.TakeLast(CampaignLogPageSize)
|
||||
.TakeLast(CampaignLogHistoryWindowSize)
|
||||
.Select(ToLogEntry)
|
||||
.ToArray();
|
||||
|
||||
@@ -1596,9 +1596,9 @@ public sealed class GameService : IGameService
|
||||
private static int NormalizeCampaignLogPageSize(int? limit)
|
||||
{
|
||||
if (!limit.HasValue)
|
||||
return CampaignLogPageSize;
|
||||
return CampaignLogLivePageSize;
|
||||
|
||||
return Math.Clamp(limit.Value, 1, CampaignLogPageSize);
|
||||
return Math.Clamp(limit.Value, 1, CampaignLogLivePageSize);
|
||||
}
|
||||
|
||||
private static UserAccount CloneUser(UserAccount user)
|
||||
@@ -1692,8 +1692,9 @@ public sealed class GameService : IGameService
|
||||
};
|
||||
}
|
||||
|
||||
private const int CampaignLogPageSize = 100;
|
||||
private static readonly JsonSerializerOptions DiceJsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private const int CampaignLogHistoryWindowSize = 100;
|
||||
private const int CampaignLogLivePageSize = 25;
|
||||
private static readonly JsonSerializerOptions DiceJsonOptions = RpgRollerJson.CreateSerializerOptions();
|
||||
private readonly Dictionary<Guid, Campaign> m_CampaignsById = [];
|
||||
private readonly Dictionary<Guid, CampaignStateTracker> m_CampaignStateById = [];
|
||||
private readonly Dictionary<Guid, Character> m_CharactersById = [];
|
||||
|
||||
Reference in New Issue
Block a user