Introduce an injected constructor taking IHardwareMonitorService, ILogger,
IRemoteWebServer (+ the concrete selection/plot/startup services) and
IStartupTracer, with shared settings initialization. A transitional 2-arg
constructor still builds the legacy graph for MainWindow until it is
DI-resolved; an _ownsServices flag keeps disposal correct across both paths.
Break the VM<->RemoteWebServer construction cycle by moving the root provider
to RemoteWebServer.SetRootProvider (removing it from the constructor), and make
RemoteWebServer.Quit idempotent. Update RemoteWebServer tests for the new ctor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All changes are localized to RemoteWebServer plus two small helpers; the
Phase 0 characterization tests confirm routing, JSON/Prometheus shape, and
credential pass/fail semantics are otherwise unchanged.
- Password hashing: new PasswordHasher uses PBKDF2-HMAC-SHA256 with a
per-credential random salt (self-describing pbkdf2$iters$salt$hash).
Verify() still accepts the legacy unsalted SHA-256 hex hash and a
successful legacy auth transparently upgrades the stored hash, persisted
by the view model on save/shutdown. Property renamed PasswordSHA256 ->
PasswordHash.
- Constant-time comparison: CredentialComparer.FixedTimeEquals for the user
name and password hash; both are evaluated fully (no && short-circuit).
- No information disclosure: POST failures return a generic message instead
of ex.ToString(); detail is logged server-side only.
- Bind intent respected: ResolveListenerIp no longer mutates ListenerIp or
silently falls back to all-interfaces for a specific configured address
(auto/'?'/wildcards still bind all). A bad address now fails Start().
- CORS: removed the Access-Control-Allow-Origin '*' wildcard; common
response headers centralized in WriteCommonHeaders.
- Prometheus: label values are escaped (EscapePrometheusLabel).
185 tests pass (was 167).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Establishes a safety net before refactoring, and lands the first
behavior-preserving cleanup.
Tests (no production behavior change):
- SensorFormatter: full per-type coverage of GetFormatString, FormatValue,
and GetPlotUnit across every SensorType.
- RemoteWebServer: extract testable seams (ResolveRoute, VerifyCredentials,
internal pure helpers) and pin routing (incl. the no-hijack rule), query
and Prometheus parsing, JSON/metric shape, credential semantics, and the
legacy SHA-256 vector.
- HardwareMonitorService: pin the enable-flag -> settings-key mapping.
- Logger: add a TimeProvider/base-dir test seam; deterministic rotation tests.
- Add [InternalsVisibleTo] for the test project.
Cleanup:
- Collapse SensorFormatter's three parallel SensorType switches into one
GetFormat source of truth; dedupe CelsiusToFahrenheit. Verified identical
by the new characterization tests.
167 tests pass (was 55).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>