Files

83 lines
2.9 KiB
C#

using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
public class PhiAccessLogService : IPhiAccessLogService
{
private readonly AppDbContext _db;
private readonly ICurrentUserService _currentUser;
private readonly IHttpContextAccessor _http;
private readonly PhiEncryptionOptions _options;
private readonly ClinicalMetrics _metrics;
public PhiAccessLogService(
AppDbContext db,
ICurrentUserService currentUser,
IHttpContextAccessor http,
IOptions<PhiEncryptionOptions> options,
ClinicalMetrics metrics)
{
_db = db;
_currentUser = currentUser;
_http = http;
_options = options.Value;
_metrics = metrics;
}
public async Task LogViewAsync(Guid patientId, string resourcePath) =>
await WriteAsync(PhiAccessType.View, patientId, resourcePath);
public async Task LogCreateAsync(Guid patientId, string resourcePath) =>
await WriteAsync(PhiAccessType.Create, patientId, resourcePath);
public async Task LogUpdateAsync(Guid patientId, string resourcePath) =>
await WriteAsync(PhiAccessType.Update, patientId, resourcePath);
public async Task LogListAsync(string resourcePath, int resultCount, string? searchQuery = null)
{
if (!_options.LogListAccess)
return;
var accessType = string.IsNullOrWhiteSpace(searchQuery)
? PhiAccessType.List
: PhiAccessType.Search;
await WriteAsync(accessType, null, resourcePath, resultCount, searchQuery);
}
private async Task WriteAsync(
PhiAccessType accessType,
Guid? patientId,
string resourcePath,
int? resultCount = null,
string? searchQuery = null)
{
if (!_currentUser.IsAuthenticated || _currentUser.UserId is null)
return; // machine/integration paths may skip — Phase 31 Integration role should still auth
_db.PhiAccessLogs.Add(new PhiAccessLog
{
Id = Guid.NewGuid(),
AccessType = accessType,
PatientId = patientId,
UserId = _currentUser.UserId.Value,
UserDisplayName = _currentUser.DisplayName ?? _currentUser.Username ?? "Unknown",
ResourcePath = resourcePath,
SearchQueryHash = searchQuery is null ? null : HashQuery(searchQuery),
ResultCount = resultCount,
IpAddress = _currentUser.IpAddress,
CorrelationId = _http.HttpContext?.Items["CorrelationId"]?.ToString(),
AccessedAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
_metrics.PhiAccessLogsTotal.WithLabels(accessType.ToDbString()).Inc();
}
private static string HashQuery(string query)
{
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(query.Trim().ToLowerInvariant()));
return Convert.ToHexString(hash).ToLowerInvariant();
}
}