Anonymous /admin/import_viewer was safe locally but exposed viewer-import surface whenever the server was reachable off-box. Gate it on a shared secret carried in X-Admin-Secret, sourced from Admin:ImportSecret in appsettings (override via ADMIN__IMPORTSECRET env var). Fails closed: an unconfigured deployment 401s every request (with a logged warning) instead of leaving the endpoint open. Implementation is a plain IAuthorizationFilter attribute (RequireAdminSecret) that runs before model binding — short-circuits the ImportViewerRequest deserialize on unauthorized calls. Compare uses CryptographicOperations.FixedTimeEquals to avoid timing signal. A companion Swagger IOperationFilter registers an ApiKey security definition and attaches the requirement ONLY to actions carrying the attribute, so the Swagger UI shows an Authorize dialog for admin endpoints without decorating ordinary game routes with a padlock. Tests: existing 27 AdminController tests routed through a new SVSimTestFactory.CreateAdminClient() helper that bakes the header from appsettings.Testing.json; added two negative-path tests (missing_secret_header, wrong_secret) — 29/29 passing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
47 lines
1.8 KiB
JSON
47 lines
1.8 KiB
JSON
{
|
|
"Logging": {
|
|
"LogLevel": {
|
|
"Default": "Information",
|
|
"Microsoft.AspNetCore": "Warning",
|
|
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information",
|
|
"Microsoft.EntityFrameworkCore": "Warning",
|
|
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
|
|
"Microsoft.EntityFrameworkCore.Infrastructure": "Warning"
|
|
}
|
|
},
|
|
"Serilog": {
|
|
"MinimumLevel": {
|
|
"Default": "Information",
|
|
"Override": {
|
|
"Microsoft.AspNetCore": "Warning",
|
|
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information",
|
|
"Microsoft.EntityFrameworkCore": "Warning",
|
|
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
|
|
"Microsoft.EntityFrameworkCore.Infrastructure": "Warning"
|
|
}
|
|
}
|
|
},
|
|
"ConnectionStrings": {
|
|
"ApplicationDb": "Host=localhost;Database=svsim;Username=postgres;password=postgres"
|
|
},
|
|
"Deck": {
|
|
"MaxDeckSlots": 36
|
|
},
|
|
"Admin": {
|
|
// Shared secret required in the X-Admin-Secret header on /admin/* util endpoints
|
|
// (see RequireAdminSecretAttribute). Leaving this blank disables the endpoint entirely
|
|
// — fail-closed so an unconfigured deployment never exposes admin surface. Override with
|
|
// the ADMIN__IMPORTSECRET env var.
|
|
"ImportSecret": "replaceme"
|
|
},
|
|
"BattleNode": {
|
|
// Socket.IO v2 endpoint URL echoed to the client on /*/do_matching success.
|
|
// Wire format: "host[:port]/socket.io/" — no scheme prefix, trailing slash REQUIRED.
|
|
// Host may be an IP, hostname, or FQDN. BestHTTP's SocketManager parses this string
|
|
// directly; a leading http:// or https:// will make the client fail to connect.
|
|
// Startup throws if this value is missing or blank; there is no hardcoded fallback.
|
|
"NodeServerUrl": "localhost:5148/socket.io/"
|
|
},
|
|
"AllowedHosts": "*"
|
|
}
|