mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 05:23:14 +00:00
Some checks failed
Deploy / Deploy to AWS (push) Has been cancelled
## 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]
52 lines
2 KiB
C#
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);
|
|
}
|
|
}
|