# Story 1.5: Shared Audit Logging Infrastructure Status: done ## Story As a a system, I want all security-relevant and operational events captured by a shared logging service, so that audit history is uniformly available across all application features from day one without per-feature implementation. ## Acceptance Criteria 1. **Given** any security-relevant action occurs (auth event, permission check, privileged update) **When** the action completes **Then** the event is written to the audit log within 5 seconds including actor identity, timestamp (UTC), action type, resource identifier, and outcome (NFR7) 2. **Given** audit records are persisted **When** the retention policy is evaluated **Then** records are retained for at least 365 days and are not purgeable by standard application operations (NFR7) 3. **Given** any application feature calls the audit logging service **When** the call is made **Then** it succeeds using the shared service contract — the calling feature does not implement its own audit persistence 4. **Given** an audit record is written **When** retrieved for review **Then** it is append-only — no update or delete operations are available on audit records 5. **Given** the audit service is unavailable **When** an auditable action occurs **Then** the action is blocked or queued — auditable operations must not silently proceed without capture ## Tasks / Subtasks - [x] Implement story behavior in aligned backend/frontend modules (AC: #1) - [x] Add or update API/service/UI components required by the story scope - [x] Keep legacy Access entities read-only and route writes to extension-layer structures - [x] Cover acceptance criteria #2 in implementation and tests (AC: #2) - [x] Add validation/error handling and UX state updates as needed - [x] Cover acceptance criteria #3 in implementation and tests (AC: #3) - [x] Add validation/error handling and UX state updates as needed - [x] Cover acceptance criteria #4 in implementation and tests (AC: #4) - [x] Add validation/error handling and UX state updates as needed - [x] Validate and document completion evidence - [x] Verify build/tests for touched modules - [x] Capture changed files and any migration/config implications ### Review Findings - [x] [Review][Patch] Audit timestamps are serialized with caller-provided offsets instead of normalized UTC [Campaign_Tracker.Server/Audit/AppendOnlyFileAuditService.cs:40] - [x] [Review][Patch] AuditEvent required fields are not validated at runtime before durable write [Campaign_Tracker.Server/Audit/IAuditService.cs:19] - [x] [Review][Patch] Token exchange and refresh authentication events bypass shared audit logging [Campaign_Tracker.Server/Controllers/AuthTokenController.cs:15] - [x] [Review][Patch] Authorization success and anonymous challenge outcomes are not uniformly audited [Campaign_Tracker.Server/Authorization/AuthorizationAuditResultHandler.cs:18] - [x] [Review][Patch] Integration test audit service ignores GetRecent maxCount contract [Campaign_Tracker.Server.Tests/AuthEndpointTests.cs:75] - [x] [Review][Patch] AppendOnlyFileAuditService.GetRecent accepts negative maxCount and can throw an incidental range exception [Campaign_Tracker.Server/Audit/AppendOnlyFileAuditService.cs:71] ## Dev Notes - Follow Epic 1 architecture constraints: ASP.NET Core + React separation, RBAC-aware patterns, and immutable legacy tables. - Reuse shared component and workflow patterns defined in UX and architecture docs; avoid parallel custom implementations. - Keep changes scoped to this story; do not pull forward Epic 2+ features. ### Project Structure Notes - Backend: `Campaign_Tracker.Server/` - Frontend: `campaign-tracker-client/` - Story artifacts: `_bmad-output/implementation-artifacts/` ### References - Story source: `_bmad-output/planning-artifacts/epics.md` (Epic 1 / Story 1.5) - Architecture constraints: `_bmad-output/planning-artifacts/architecture.md` - UX patterns: `_bmad-output/planning-artifacts/ux-design-specification.md` ## Dev Agent Record ### Agent Model Used claude-sonnet-4-6 ### Debug Log References - Story generated from epic source and architecture/UX planning artifacts. - 2026-05-05: Created IAuditService, AuditEvent, AppendOnlyFileAuditService in Audit/ folder. - 2026-05-05: Updated InMemoryAuthenticationAuditStore to delegate to IAuditService. - 2026-05-05: Registered IAuditService in Program.cs (configurable via Audit:LogDirectory). - 2026-05-05: Wrote AuditServiceTests (8 tests covering ACs #1–#5). - 2026-05-05: Added AC #5 integration test; refactored AuthEndpointTests to use AuthIntegrationTestFactory. - 2026-05-05: dotnet test passed — 23/23 tests. ### Completion Notes List - Story context created and marked ready-for-dev. - Created `IAuditService` + `AuditEvent` general-purpose contract. `AuditEventType` constants centralise event names (SESSION_LOGIN, SESSION_LOGOUT, etc.). Interface is intentionally append-only: only `Record()` and `GetRecent()` — no delete or update methods (AC #4). - Created `AppendOnlyFileAuditService`: writes JSON Lines to daily-rotating `.jsonl` files in a configurable directory. Files are never deleted by the service (AC #2). `Record()` wraps IO failures in `AuditServiceUnavailableException` and propagates, blocking the caller (AC #5). - Updated `InMemoryAuthenticationAuditStore` to accept `IAuditService` via constructor injection. Each `Record*()` method enqueues to the in-memory queue (for fast test assertions) AND calls `IAuditService.Record()` for durable persistence. Exceptions from `IAuditService.Record()` propagate to the caller per AC #5. - Registered `IAuditService → AppendOnlyFileAuditService` in `Program.cs`. Log directory is configurable via `Audit:LogDirectory` configuration key (defaults to `/audit-logs`). - `InMemoryAuthenticationAuditStore` now uses constructor DI for `IAuditService` — satisfies AC #3 (calling features use shared contract, not own persistence). - Refactored `AuthEndpointTests` to use `AuthIntegrationTestFactory` (custom `WebApplicationFactory` subclass) that injects an in-memory `IAuditService` passthrough. This keeps integration tests file-system-independent while the real file service is validated by `AuditServiceTests`. AC #5 integration test confirms that a failing `IAuditService` blocks the authenticated session endpoint. - All 23 backend tests pass. - Applied review fixes: audit timestamps are normalized to UTC, audit required fields are validated before durable writes, `GetRecent` has bounded argument handling, token exchange/refresh paths write shared audit events, and authorization allowed/challenged/denied outcomes are centrally audited. - 2026-05-06: `dotnet test .\Campaign_Tracker.Server.Tests\Campaign_Tracker.Server.Tests.csproj /p:UseAppHost=false` passed (86 tests). - 2026-05-06: `dotnet build .\campaign-tracker.sln /p:UseAppHost=false` passed with 0 warnings and 0 errors. ### File List - `Campaign_Tracker.Server/Audit/IAuditService.cs` - `Campaign_Tracker.Server/Audit/AppendOnlyFileAuditService.cs` - `Campaign_Tracker.Server/Controllers/AuthTokenController.cs` - `Campaign_Tracker.Server/Authorization/AuthorizationAuditResultHandler.cs` - `Campaign_Tracker.Server/Authentication/InMemoryAuthenticationAuditStore.cs` - `Campaign_Tracker.Server/Program.cs` - `Campaign_Tracker.Server.Tests/AuditServiceTests.cs` - `Campaign_Tracker.Server.Tests/AuthEndpointTests.cs` - `_bmad-output/implementation-artifacts/1-5-shared-audit-logging-infrastructure.md` - `_bmad-output/implementation-artifacts/sprint-status.yaml` ### Change Log | Date | Version | Description | Author | | --- | --- | --- | --- | | 2026-05-05 | 1.0 | Implemented shared audit logging infrastructure: IAuditService, AppendOnlyFileAuditService, InMemoryAuthenticationAuditStore refactor, Program.cs registration, AuditServiceTests, AuthEndpointTests refactor. 23/23 tests passing. | Amelia (Dev) | | 2026-05-06 | 1.1 | Applied code-review fixes for UTC normalization, required-field validation, token/authorization audit coverage, and bounded recent-event retrieval. 86/86 backend tests passing. | GPT-5 Codex |