using System.Security.Claims; using System.Security.Cryptography; using System.Text; using Hl7.Fhir.Serialization; using Microsoft.Extensions.Options; using Task = System.Threading.Tasks.Task; public class FhirApiKeyOrJwtMiddleware { public const string SchemeName = "FhirApiKey"; private readonly RequestDelegate _next; private readonly FhirOptions _options; private static readonly FhirJsonSerializer Serializer = new(); public FhirApiKeyOrJwtMiddleware(RequestDelegate next, IOptions options) { _next = next; _options = options.Value; } public async Task InvokeAsync(HttpContext context) { if (!context.Request.Path.StartsWithSegments("/fhir")) { await _next(context); return; } if (context.Request.Path.StartsWithSegments("/fhir/R4/metadata")) { await _next(context); return; } if (context.User.Identity?.IsAuthenticated == true) { await _next(context); return; } var configuredKeys = GetConfiguredKeys(); if (configuredKeys.Length == 0) { await _next(context); return; } if (context.Request.Headers.TryGetValue("X-Api-Key", out var suppliedKey) && MatchesAnyKey(suppliedKey!, configuredKeys)) { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, "44444444-4444-4444-4444-444444444444"), new Claim(ClaimTypes.Name, "integration.mirth"), new Claim("display_name", "Mirth Connect"), new Claim("clinical_role", ClinicalRole.Integration.ToDbString()), }; context.User = new ClaimsPrincipal(new ClaimsIdentity(claims, SchemeName)); await _next(context); return; } if (context.Request.Headers.ContainsKey("X-Api-Key")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/fhir+json"; var outcome = FhirOperationOutcomeBuilder.Create(401, "login", "Invalid or missing API key."); await context.Response.WriteAsync(Serializer.SerializeToString(outcome)); return; } await _next(context); } private string[] GetConfiguredKeys() { var keys = new List(); if (!string.IsNullOrWhiteSpace(_options.ApiKey)) keys.Add(_options.ApiKey); foreach (var k in _options.ApiKeys) { if (!string.IsNullOrWhiteSpace(k)) keys.Add(k); } return keys.ToArray(); } private static bool MatchesAnyKey(string supplied, string[] configuredKeys) { var suppliedBytes = Encoding.UTF8.GetBytes(supplied); var matched = false; foreach (var configured in configuredKeys) { var configuredBytes = Encoding.UTF8.GetBytes(configured); if (CryptographicOperations.FixedTimeEquals(suppliedBytes, configuredBytes)) matched = true; } return matched; } }