Makes the application say what it is doing and when it fails
U4. Console logging plus Sentry, a same-origin tunnel so ad blockers cannot silence browser errors, Umami on the admin SPA, and six security events that alert rules can actually be built on. The correlation id is the W3C trace id from the ambient Activity, enabled by one line of ActivityTrackingOptions so every entry from every category carries it without touching a call site. It propagates across the master/slave boundary via traceparent, which TraceIdentifier cannot do at all, and it is the same value ProblemDetails already returns to the browser. The security events use source-generated LoggerMessage with constant templates. Sentry groups log events by message, so interpolating an email address would give every address its own issue and "more than 20 failed logins in five minutes" could never fire — the events would arrive, be visible, be tagged, and the alerting would silently be impossible. A test asserts the rendered message is identical across argument values. Scrubbing happens in-process, before transmission, and covers Set-Cookie as well as Cookie: the login response issues the refreshToken there, so scrubbing only the request side would protect nothing. Transactions are scrubbed too, because they carry request data and are the channel nobody thinks of. The tunnel derives its destination from the DSN once at startup and reads nothing from the request, which is what separates a tunnel from a server-side request forgery primitive. Size is capped by a bounded read rather than by trusting Content-Length, and the endpoint is rate limited. Two things found along the way. Zod 4's url() hands the value to the URL constructor, which accepts any scheme — so the existing frontend validation would have accepted the exact "htp://" typo BR-U4-24 names, and the SPA would have called a nonexistent origin. Now constrained to http(s). And the new appsettings comments are verified against the real configuration provider, because the failure mode if it rejected them is both hosts refusing to start after a release switch. One deviation. IAdminTokenValidator was meant to gain a reason-reporting overload; implemented that way, a substitute returning false by default silently inverted the access decision while both methods compiled. Two methods whose difference is invisible at the call site is the defect, so it is now a single Validate returning AdminTokenResult. Touches two files from already-committed units: DatabaseMigrationExtensions (U2) gains a flush before the rethrow, or the one Critical event in the system dies with the process; AdminTokenValidator (U1) classifies why a bypass was refused. Build 0 errors; 366 backend tests pass, up from 315, and 237 frontend tests, up from 213. tsc clean, eslint clean on every changed file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using SlpModularCms.Api.Extensions;
|
||||
using SlpModularCms.Core.Hosting;
|
||||
using SlpModularCms.Core.Hosting.Health;
|
||||
using SlpModularCms.Core.Hosting.Observability;
|
||||
using SlpModularCms.Core.Hosting.Security;
|
||||
using Scalar.AspNetCore;
|
||||
|
||||
@@ -9,6 +10,15 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// Load local developer overrides
|
||||
builder.Configuration.AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true);
|
||||
|
||||
// Logging FIRST, so a problem initialising Sentry below is itself logged. Puts the W3C trace id
|
||||
// into the scope of every entry from every category — the correlation id that also travels to
|
||||
// the slave via traceparent and appears as `traceId` in ProblemDetails responses.
|
||||
builder.Logging.AddCmsLogging(builder.Environment);
|
||||
|
||||
// Then Sentry. Does nothing at all when no DSN is configured, which is a normal, fully
|
||||
// supported state rather than an error.
|
||||
builder.WebHost.UseCmsSentry(builder.Configuration);
|
||||
|
||||
// 1. Initialize Module Orchestrator
|
||||
var loggerFactory = LoggerFactory.Create(lb => lb.AddConsole());
|
||||
var orchestrator = new ModuleOrchestrator(loggerFactory.CreateLogger<ModuleOrchestrator>());
|
||||
@@ -20,6 +30,7 @@ builder.Services.AddCmsCors(builder.Configuration);
|
||||
builder.Services.AddCmsRateLimiting(builder.Configuration);
|
||||
builder.Services.AddCmsHealthChecks();
|
||||
builder.Services.AddCmsSecurityHeaders(builder.Configuration);
|
||||
builder.Services.AddCmsObservability(builder.Configuration);
|
||||
|
||||
// Registered BEFORE module services: modules must not configure Data Protection themselves,
|
||||
// because a later registration would override this persistent key store (see
|
||||
@@ -93,6 +104,13 @@ app.MapControllers();
|
||||
// (/api/v1/System/capabilities). Those are CMS domain state and must not be used for monitoring.
|
||||
app.MapCmsHealthChecks();
|
||||
|
||||
// Forwards browser Sentry envelopes through this origin, because ad blockers block requests to
|
||||
// Sentry domains outright. Mapped before the SPA catch-all below, and deliberately NOT on the
|
||||
// availability gate's bypass list: if the instance is switched off, losing admin-SPA error
|
||||
// reports is acceptable, and that is one fewer anonymous outbound-capable endpoint reachable on
|
||||
// a disabled instance.
|
||||
app.MapSentryTunnel();
|
||||
|
||||
// SPA fallbacks so client-side routes (e.g. /admin/dashboard) resolve to the right index.html
|
||||
// instead of 404ing. The "nonfile" constraint keeps genuinely missing assets (e.g. /admin/assets/x.js) as 404s.
|
||||
app.MapCmsSpaFallbacks();
|
||||
|
||||
Reference in New Issue
Block a user