112 lines
4.3 KiB
C#
112 lines
4.3 KiB
C#
using System.Reflection;
|
|
using FinalFactory.Rendezvous.Contracts;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace FinalFactory.Rendezvous.Server.Diagnostics;
|
|
|
|
internal static class DiagnosticDashboardEndpoints
|
|
{
|
|
private const string ContentSecurityPolicy =
|
|
"default-src 'none'; base-uri 'none'; connect-src 'self'; "
|
|
+ "font-src 'self'; form-action 'none'; frame-ancestors 'none'; "
|
|
+ "img-src 'self'; manifest-src 'none'; object-src 'none'; "
|
|
+ "script-src 'self'; style-src 'self'";
|
|
private static readonly byte[] Index = ReadAsset("index.html");
|
|
private static readonly byte[] Script = ReadAsset("app.mjs");
|
|
private static readonly byte[] Styles = ReadAsset("styles.css");
|
|
|
|
public static IEndpointRouteBuilder MapDiagnosticDashboardEndpoints(
|
|
this IEndpointRouteBuilder endpoints)
|
|
{
|
|
RouteGroupBuilder dashboard = endpoints.MapGroup("/diagnostics")
|
|
.ExcludeFromDescription();
|
|
dashboard.MapGet("", ServeIndex);
|
|
dashboard.MapGet("/app.mjs", ServeScript);
|
|
dashboard.MapGet("/styles.css", ServeStyles);
|
|
dashboard.MapGet("/config.json", ServeConfiguration);
|
|
return endpoints;
|
|
}
|
|
|
|
private static IResult ServeIndex(
|
|
HttpContext context,
|
|
[FromServices] IOptions<DiagnosticDashboardOptions> configured) =>
|
|
ServeAsset(context, configured.Value, Index, "text/html; charset=utf-8");
|
|
|
|
private static IResult ServeScript(
|
|
HttpContext context,
|
|
[FromServices] IOptions<DiagnosticDashboardOptions> configured) =>
|
|
ServeAsset(context, configured.Value, Script, "text/javascript; charset=utf-8");
|
|
|
|
private static IResult ServeStyles(
|
|
HttpContext context,
|
|
[FromServices] IOptions<DiagnosticDashboardOptions> configured) =>
|
|
ServeAsset(context, configured.Value, Styles, "text/css; charset=utf-8");
|
|
|
|
private static IResult ServeConfiguration(
|
|
HttpContext context,
|
|
[FromServices] IOptions<DiagnosticDashboardOptions> configured)
|
|
{
|
|
DiagnosticDashboardOptions options = configured.Value;
|
|
if (!options.Enabled)
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
ApplySecurityHeaders(context.Response);
|
|
return Results.Json(new
|
|
{
|
|
contractVersion = ContractLimits.ContractVersion,
|
|
pollIntervalSeconds = options.PollIntervalSeconds,
|
|
maximumRenderedSessions = options.MaximumRenderedSessions,
|
|
scopes = options.Scopes.Select(static scope => new
|
|
{
|
|
gameId = scope.GameId,
|
|
environmentId = scope.EnvironmentId,
|
|
protocolVersions = scope.ProtocolVersions,
|
|
regions = scope.Regions,
|
|
visibility = "public",
|
|
}),
|
|
});
|
|
}
|
|
|
|
private static IResult ServeAsset(
|
|
HttpContext context,
|
|
DiagnosticDashboardOptions options,
|
|
byte[] content,
|
|
string contentType)
|
|
{
|
|
if (!options.Enabled)
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
ApplySecurityHeaders(context.Response);
|
|
return Results.Bytes(content, contentType);
|
|
}
|
|
|
|
private static void ApplySecurityHeaders(HttpResponse response)
|
|
{
|
|
response.Headers.CacheControl = "no-store";
|
|
response.Headers["Content-Security-Policy"] = ContentSecurityPolicy;
|
|
response.Headers["X-Content-Type-Options"] = "nosniff";
|
|
response.Headers["X-Frame-Options"] = "DENY";
|
|
response.Headers["Cross-Origin-Opener-Policy"] = "same-origin";
|
|
response.Headers["Cross-Origin-Resource-Policy"] = "same-origin";
|
|
response.Headers["Permissions-Policy"] =
|
|
"camera=(), geolocation=(), microphone=(), payment=(), usb=()";
|
|
response.Headers["Referrer-Policy"] = "no-referrer";
|
|
}
|
|
|
|
private static byte[] ReadAsset(string fileName)
|
|
{
|
|
Assembly assembly = typeof(DiagnosticDashboardEndpoints).Assembly;
|
|
string resourceName = $"FinalFactory.Rendezvous.Server.Diagnostics.Assets.{fileName}";
|
|
using Stream source = assembly.GetManifestResourceStream(resourceName)
|
|
?? throw new InvalidOperationException($"Missing embedded dashboard asset {fileName}.");
|
|
using MemoryStream destination = new();
|
|
source.CopyTo(destination);
|
|
return destination.ToArray();
|
|
}
|
|
}
|