# Components Component definitions and high-level responsibilities. Detailed business logic is designed per unit in Functional Design. **Placement rule (Q1 = A)**: every new cross-cutting concern lives in `SlpModularCms.Core` and is exposed as an extension method. Both hosts therefore get identical behaviour with no duplicated implementation. **Q2 = A**: `SlpModularCms.Api.Slave` receives everything except the static-file mounts, because it serves as a reference for what a customer-facing API instance looks like — not merely as a local dev tool. --- ## New Components ### C-01 `SecurityHeadersMiddleware` - **Project**: `SlpModularCms.Core` (`Hosting/Security/`) - **Purpose**: Apply HTTP security headers that would normally come from nginx or IIS configuration, which NFR-01 forbids relying on. - **Responsibilities**: - Attach headers at response start, so the response content type is known before deciding what applies - Apply per-header scoping (FU1 = A): `X-Content-Type-Options` and `Strict-Transport-Security` on **all** responses; `Content-Security-Policy`, `X-Frame-Options` and `Referrer-Policy` on **HTML** responses only - Select the CSP policy for the request path - Never overwrite a header another component has already set - **Interfaces**: standard middleware — `InvokeAsync(HttpContext)`. Registered via `UseCmsSecurityHeaders()`. - **Requirements**: FR-18, SECURITY-04 ### C-02 `SecurityHeadersOptions` - **Project**: `SlpModularCms.Core` (`Hosting/Security/`) - **Purpose**: Bind the `SecurityHeaders` configuration section (Options pattern, consistent with `JwtSettings`, `MasterModule`, `MasterPolling`). - **Responsibilities**: Carry the path-to-policy assignment, the default policy, and the environment-specific allowed origins. - **Design rule (Q5 = B + Q6 = B, confirmed FU2 = A)**: **policy definitions live in code; path assignment and origins live in configuration.** Adding a path later needs no code change; inventing a new policy does. - **Requirements**: FR-18 ### C-03 `CspPolicyBuilder` - **Project**: `SlpModularCms.Core` (`Hosting/Security/`) - **Purpose**: Compose a Content-Security-Policy string from a named policy plus the configured origins. - **Responsibilities**: - Define the two named policies in code: `Strict` (for `/admin` and `/api/v1`) and `Relaxed` (for the public website) - Inject the configured Umami script origin and Sentry ingest origin into the relevant directives - Build each policy once at startup rather than per request - **Rationale for existing separately from C-01**: keeps policy composition unit-testable without a request pipeline, and keeps the middleware free of string building. - **Requirements**: FR-18, D-31 ### C-04 Health-check registration - **Project**: `SlpModularCms.Core` (`Hosting/Health/`) - **Purpose**: Expose infrastructure liveness, strictly separate from CMS domain state. - **Responsibilities**: - Register the framework health-check services (no package required) - Map `GET /health` returning `200`/`Healthy` or `503`/`Unhealthy` - **Liveness only** — no database call, no dependency probing (D-21) - **Explicit non-responsibility**: this component says nothing about availability or capabilities. Those are CMS domain functionality and are never to be used for monitoring. - **Requirements**: FR-10 ### C-05 `CmsDataProtection` registration - **Project**: `SlpModularCms.Core` (`Hosting/`) - **Purpose**: Persist the Data Protection key ring in the database so redeploys and atomic release switches cannot render stored slave API keys unreadable. - **Responsibilities**: Configure `PersistKeysToDbContext` and set a stable application discriminator so both hosts and all replicas derive the same keys. - **Requirements**: FR-12, D-17 ### C-06 `ApplicationDbContext` extension — `IDataProtectionKeyContext` - **Project**: `SlpModularCms.Core` (`Data/`) — **modification of an existing component** - **Purpose**: Host the Data Protection keys table (Q7 = A). - **Responsibilities**: Add `DbSet DataProtectionKeys` and implement `IDataProtectionKeyContext`. Requires one new Core migration. - **Why here rather than a fourth context**: keys are application-wide infrastructure, not module-owned, and `ApplicationDbContext` now migrates automatically (FR-11) so the table is created without manual steps. - **Requirements**: FR-12 ### C-07 Startup migration runner - **Project**: `SlpModularCms.Core` (`Hosting/`) - **Purpose**: Apply `ApplicationDbContext` migrations at startup, removing the need for CLI access on the host. - **Responsibilities**: - Run `Database.Migrate()` for `ApplicationDbContext` during startup, before the request pipeline accepts traffic - **Fail fast (Q8 = A)**: on failure, let the exception propagate so the process does not start - **Design interaction worth stating**: fail-fast is what makes the liveness-only health check meaningful. A process that cannot migrate never starts, `/health` stops answering, and UptimeRobot goes red. Had this logged-and-continued, the app would look healthy while being unusable. - **Requirements**: FR-11, D-13 ### C-08 `CmsLogging` registration - **Project**: `SlpModularCms.Core` (`Hosting/Observability/`) - **Purpose**: Configure structured logging, independent of whether Sentry is enabled (Q10 = B). - **Responsibilities**: Console logging with structured output and a correlation/request identifier on every entry; exclude secrets and PII. - **Open item**: OPEN-01 — the correlation-ID mechanism (ASP.NET Core `TraceIdentifier` versus W3C `traceparent`) is decided in NFR Design for Unit 4. - **Requirements**: D-20, SECURITY-03 ### C-09 `CmsSentry` registration - **Project**: `SlpModularCms.Core` (`Hosting/Observability/`) - **Purpose**: Report errors and structured logs to Sentry, tagged by environment. - **Responsibilities**: - Initialise `Sentry.AspNetCore` when a DSN is configured, and **skip silently when it is not**, leaving console logging active - Tag events with environment and release - Emit security-relevant events for alerting (FR-19) - **Separate from C-08 (Q10 = B)**: structured logging must work without Sentry. - **Requirements**: FR-14, FR-19, D-19 ### C-10 Static-file mount composition - **Project**: `SlpModularCms.Api` **only** — host-specific, not in `Core` (Q2 = A: the Slave has no static content) - **Purpose**: Serve two independent front-ends from one process. - **Responsibilities** (Q3 = A): - Mount `wwwroot/web/` at `/` with its own `PhysicalFileProvider` - Mount `wwwroot/admin/` at `/admin` with its own `PhysicalFileProvider` - Provide default-file handling per mount - Map two SPA fallbacks, preserving the `nonfile` route constraint so missing assets still return `404` - **Design consequence**: two explicit registrations rather than one, so each mount can later carry its own headers or caching without disturbing the other. - **Requirements**: FR-07, FR-08, D-06 ### C-11 Deploy transport workflows - **Project**: `.gitea/workflows/` — not C# - **Purpose**: Transfer a published release to a target host. - **Responsibilities** (Q11 = B): one reusable workflow per transport, sharing an identical input interface. `deploy-scp.yaml` is implemented now; `deploy-ftps.yaml` can be added later without changing callers. - **Requirements**: FR-02, D-02, NFR-09 ### C-12 CI workflow - **Project**: `.gitea/workflows/continuous_integration.yaml` - **Purpose**: Validate every change and drive deployment. - **Responsibilities**: Six blocking gates, two environment-specific builds, artifact publication, and the calls into C-11 for test and production. - **Requirements**: FR-01, FR-03, FR-04, FR-05 --- ## Modified Existing Components ### C-13 `AvailabilityMiddleware` - **Project**: `SlpModularCms.Modules.Availability` (`Middleware/`) - **Changes**: 1. Add `/health` to `_bypassPrefixes` so the availability gate cannot mask infrastructure liveness (FR-10, D-22) 2. **Fix `IsAdminBypass` to stop trusting an unvalidated token** (FR-24, Q12 = A) — currently `ReadJwtToken` parses without signature verification, so an unauthenticated caller can forge an `Owner` claim and bypass the gate - **Behaviour that must be preserved**: an Owner or Administrator with a *valid* token still passes, so administrators can always reach a disabled instance to switch it back on. - **Requirements**: FR-10, FR-24 ### C-14 Frontend application configuration (`frontend/src/lib/config.ts`) - **Changes**: treat an absent or empty `VITE_API_BASE_URL` as same-origin while still accepting an explicit absolute URL for local development against `https://localhost:7221` / `:7222`. Zod validation relaxed accordingly, without silently accepting malformed values. - **Requirements**: FR-13, D-14 ### C-15 Frontend observability - **Project**: `frontend/src/` - **Changes**: initialise `@sentry/react` with environment and release tags, skipping gracefully without a DSN; add the Umami tracking script with a per-environment website ID, absent during local development. - **Requirements**: FR-15, FR-16 ### C-16 Host composition (`Program.cs`, both hosts) - **Changes**: call the new `Core` extension methods in the correct order. **Q9 = A**: the two files stay separate — with the implementation in `Core`, what remains duplicated is an explicit list of calls, which is intentional readability rather than accidental duplication. - **Requirements**: FR-07, FR-10, FR-11, FR-12, FR-14, FR-18 --- ## Component Summary | ID | Component | Project | Type | Slave gets it? | |---|---|---|---|---| | C-01 | `SecurityHeadersMiddleware` | Core | New | Yes | | C-02 | `SecurityHeadersOptions` | Core | New | Yes | | C-03 | `CspPolicyBuilder` | Core | New | Yes | | C-04 | Health-check registration | Core | New | Yes | | C-05 | `CmsDataProtection` registration | Core | New | Yes | | C-06 | `ApplicationDbContext` keys table | Core | Modified | Yes | | C-07 | Startup migration runner | Core | New | Yes | | C-08 | `CmsLogging` registration | Core | New | Yes | | C-09 | `CmsSentry` registration | Core | New | Yes | | C-10 | Static-file mount composition | Api | New | **No** | | C-11 | Deploy transport workflows | `.gitea/` | New | n/a | | C-12 | CI workflow | `.gitea/` | New | n/a | | C-13 | `AvailabilityMiddleware` | Modules.Availability | Modified | Yes | | C-14 | Frontend config | frontend | Modified | n/a | | C-15 | Frontend observability | frontend | New | n/a | | C-16 | Host composition | Both hosts | Modified | Yes | **14 code components** (9 new, 5 modified) plus **2 workflow components**.