|
- using System.Collections.Concurrent;
- using Campaign_Tracker.Server.Audit;
-
- namespace Campaign_Tracker.Server.Authentication;
-
- /// <summary>
- /// Implements IAuthenticationAuditStore by delegating durable writes to the shared
- /// IAuditService and maintaining an in-process queue for fast test/review queries.
- ///
- /// Both the in-memory enqueue and the IAuditService.Record() call happen synchronously.
- /// If IAuditService.Record() throws (audit store unavailable), the exception propagates
- /// to the caller — auditable actions are blocked rather than silently proceeding (AC #5).
- /// </summary>
- public sealed class InMemoryAuthenticationAuditStore : IAuthenticationAuditStore
- {
- private readonly ConcurrentQueue<AuthenticationAuditEvent> _events = new();
- private readonly IAuditService _auditService;
-
- public InMemoryAuthenticationAuditStore(IAuditService auditService)
- {
- _auditService = auditService;
- }
-
- public IReadOnlyCollection<AuthenticationAuditEvent> Events => _events.ToArray();
-
- public void RecordSuccess(string subject, string traceIdentifier)
- {
- var now = DateTimeOffset.UtcNow;
- var authEvent = new AuthenticationAuditEvent(
- AuthenticationAuditEventType.Success,
- subject,
- "authenticated",
- "authentication",
- traceIdentifier,
- now);
- _auditService.Record(new AuditEvent(
- AuditEventType.SessionLogin, subject, "authentication", "success", traceIdentifier, now));
- _events.Enqueue(authEvent);
- }
-
- public void RecordFailure(string reason, string traceIdentifier)
- {
- var now = DateTimeOffset.UtcNow;
- var authEvent = new AuthenticationAuditEvent(
- AuthenticationAuditEventType.Failure,
- "anonymous",
- reason,
- "authentication",
- traceIdentifier,
- now);
- _auditService.Record(new AuditEvent(
- AuditEventType.SessionLoginFailure, "anonymous", "authentication", reason, traceIdentifier, now));
- _events.Enqueue(authEvent);
- }
-
- public void RecordAuthorizationAllowed(string subject, string resource, string traceIdentifier)
- {
- var now = DateTimeOffset.UtcNow;
- var authEvent = new AuthenticationAuditEvent(
- AuthenticationAuditEventType.AuthorizationAllowed,
- subject,
- "authorization allowed",
- resource,
- traceIdentifier,
- now);
- _auditService.Record(new AuditEvent(
- AuditEventType.AuthorizationAllowed, subject, resource, "allowed", traceIdentifier, now));
- _events.Enqueue(authEvent);
- }
-
- public void RecordAuthorizationDenied(string subject, string resource, string traceIdentifier)
- {
- var now = DateTimeOffset.UtcNow;
- var authEvent = new AuthenticationAuditEvent(
- AuthenticationAuditEventType.AuthorizationDenied,
- subject,
- "authorization denied",
- resource,
- traceIdentifier,
- now);
- _auditService.Record(new AuditEvent(
- AuditEventType.AuthorizationDenied, subject, resource, "denied", traceIdentifier, now));
- _events.Enqueue(authEvent);
- }
-
- public void RecordLogout(string subject, bool succeeded, string traceIdentifier)
- {
- var now = DateTimeOffset.UtcNow;
- var outcome = succeeded ? "session destroyed" : "session destruction failed - keycloak unreachable";
- var authEvent = new AuthenticationAuditEvent(
- AuthenticationAuditEventType.Logout,
- subject,
- outcome,
- "authentication/logout",
- traceIdentifier,
- now);
- _auditService.Record(new AuditEvent(
- AuditEventType.SessionLogout, subject, "authentication/logout",
- succeeded ? "success" : "failure", traceIdentifier, now));
- _events.Enqueue(authEvent);
- }
- }
|