proposal-system/api/src/ProposalSystem.Api/Middleware/InternalApiKeyMiddleware.cs
Adam Moussa 9d856a9619
Some checks failed
Deploy / Deploy to AWS (push) Has been cancelled
Phase 3 audit fixes: FIX-01–47, accessibility NITs, code quality NITs [skip deploy]
## Summary
Implements Phase 3 of the AUDIT-2026-05-20 findings:
- 29 FIX-severity items across API, web, infra, and lambdas
- 7 accessibility NITs (aria-labels, document titles)
- 4 code quality NITs (deduplication, constants extraction)

Key changes:
- API: N+1 fix, pagination clamping, idempotent transitions, upload confirm endpoint, revision TotalBidAmount carry-forward
- Web: confirmation dialogs, currency formatting, error states, date range filters, document titles
- Infra: S3 CORS lockdown, API Gateway throttling, AOSS network policy fix, CI concurrency
- Lambdas: skip empty suggestions, remove status side-effect
- Scripts: post-deploy health check

## Test plan
- [x] tsc --noEmit (web + infra)
- [x] dotnet build (api)
- [x] ruff check + format (lambdas)
- [x] Cross-review via orchestrator (no blockers)

[skip deploy]
2026-05-20 19:38:36 -04:00

52 lines
2 KiB
C#

using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
namespace ProposalSystem.Api.Middleware;
public class InternalApiKeyMiddleware
{
private readonly RequestDelegate _next;
private readonly byte[] _apiKeyBytes;
private readonly ILogger<InternalApiKeyMiddleware> _logger;
public InternalApiKeyMiddleware(RequestDelegate next, IConfiguration configuration, ILogger<InternalApiKeyMiddleware> logger)
{
_next = next;
_logger = logger;
var key = configuration["INTERNAL_API_KEY"] ?? "";
_apiKeyBytes = Encoding.UTF8.GetBytes(key);
}
public async Task InvokeAsync(HttpContext context)
{
if (_apiKeyBytes.Length > 0 &&
context.Request.Headers.TryGetValue("X-Internal-Api-Key", out var providedKey) &&
!string.IsNullOrEmpty(providedKey.ToString()))
{
var providedBytes = Encoding.UTF8.GetBytes(providedKey.ToString());
if (CryptographicOperations.FixedTimeEquals(providedBytes, _apiKeyBytes))
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, "system"),
new Claim("sub", "system-lambda-caller"),
new Claim(ClaimTypes.Email, "system@proposal-system.internal"),
new Claim("email", "system@proposal-system.internal"),
new Claim("name", "System"),
new Claim(ClaimTypes.Role, "admins"),
new Claim("cognito:groups", "admins"),
};
var identity = new ClaimsIdentity(claims, "InternalApiKey");
context.User = new ClaimsPrincipal(identity);
}
else
{
_logger.LogWarning("Invalid internal API key from {RemoteIp} on {Path}",
context.Connection.RemoteIpAddress, context.Request.Path);
}
}
await _next(context);
}
}