mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 14:41:13 +00:00
f979e18811
Option B per user decision: production ships with empty Jwt.Issuer / Jwt.Audience in appsettings.json so the API process refuses to start unless JWT_ISSUER + JWT_AUDIENCE env vars are supplied. Development ships with grep-friendly DEV-ONLY- placeholders so local + docker flows keep working unchanged. AuthenticationServiceCollectionExtensions flips ValidateIssuer + ValidateAudience to true and wires ValidIssuer / ValidAudience via a new ResolveRequiredOrThrow helper that all three required values (secret, iss, aud) now share. JwtTokenFactory.Create + CreateExpired gain optional iss / aud parameters (default null) so existing call sites compile unchanged. JwtTestHelpers adds MintAuthenticated / MintExpired wrappers that resolve iss + aud from env, plus ResolveIssuerOrThrow / ResolveAudienceOrThrow. PerfBootstrap.MintToken + Program.cs JWT bootstrap migrated to the new surface so the perf harness and the integration runner both validate against the same contract. Adds 4 fail-fast unit tests (missing/empty issuer + audience), 2 negative integration scenarios (WrongIssuer_Returns401, WrongAudience_Returns401), and re-tags every existing integration mint site via MintAuthenticated. Compose, .env.example, run-tests.sh, run-performance-tests.sh all load + export JWT_ISSUER + JWT_AUDIENCE alongside JWT_SECRET. Resolves F-AUTH-2 (security_report.md + owasp_review.md). AC-7 (cross-repo suite/_docs/10_auth.md write) deferred — outside this workspace; tracked in deploy_cycle2.md R3 follow-up. Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
3.6 KiB
C#
97 lines
3.6 KiB
C#
using System.Net.Http.Headers;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using SatelliteProvider.TestSupport;
|
|
|
|
namespace SatelliteProvider.IntegrationTests;
|
|
|
|
public static class JwtTestHelpers
|
|
{
|
|
public const string JwtSecretEnvVar = "JWT_SECRET";
|
|
public const string JwtIssuerEnvVar = "JWT_ISSUER";
|
|
public const string JwtAudienceEnvVar = "JWT_AUDIENCE";
|
|
public const string DefaultSubject = "integration-tests";
|
|
|
|
public static string ResolveSecretOrThrow()
|
|
{
|
|
var secret = Environment.GetEnvironmentVariable(JwtSecretEnvVar);
|
|
if (string.IsNullOrWhiteSpace(secret))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{JwtSecretEnvVar} is not set in the integration test environment. " +
|
|
"It must match the JWT_SECRET configured for the API container.");
|
|
}
|
|
|
|
var byteLength = Encoding.UTF8.GetByteCount(secret);
|
|
if (byteLength < 32)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{JwtSecretEnvVar} is {byteLength} bytes; the test runner requires at least 32 bytes to match API validation.");
|
|
}
|
|
|
|
return secret;
|
|
}
|
|
|
|
// AZ-494: runner-side resolvers for the iss / aud values the API enforces.
|
|
// Both MUST be present and match the API container's values, or the API
|
|
// will reject every minted token at validation time.
|
|
public static string ResolveIssuerOrThrow()
|
|
{
|
|
var issuer = Environment.GetEnvironmentVariable(JwtIssuerEnvVar);
|
|
if (string.IsNullOrWhiteSpace(issuer))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{JwtIssuerEnvVar} is not set in the integration test environment. " +
|
|
"It must match the JWT_ISSUER configured for the API container (AZ-494).");
|
|
}
|
|
return issuer;
|
|
}
|
|
|
|
public static string ResolveAudienceOrThrow()
|
|
{
|
|
var audience = Environment.GetEnvironmentVariable(JwtAudienceEnvVar);
|
|
if (string.IsNullOrWhiteSpace(audience))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{JwtAudienceEnvVar} is not set in the integration test environment. " +
|
|
"It must match the JWT_AUDIENCE configured for the API container (AZ-494).");
|
|
}
|
|
return audience;
|
|
}
|
|
|
|
public static void AttachDefaultAuthorization(HttpClient httpClient, string token)
|
|
{
|
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
}
|
|
|
|
// AZ-494: convenience wrapper. Reads JWT_ISSUER / JWT_AUDIENCE from env
|
|
// (cached for the runner's lifetime via resolve calls) and delegates to
|
|
// the canonical JwtTokenFactory.Create. Test scenarios that need WRONG
|
|
// iss/aud (e.g. negative AC tests) pass overrides explicitly.
|
|
public static string MintAuthenticated(
|
|
string secret,
|
|
string? subject = null,
|
|
IEnumerable<Claim>? extraClaims = null,
|
|
TimeSpan? lifetime = null,
|
|
string? overrideIssuer = null,
|
|
string? overrideAudience = null)
|
|
{
|
|
return JwtTokenFactory.Create(
|
|
secret,
|
|
subject ?? DefaultSubject,
|
|
lifetime,
|
|
extraClaims,
|
|
issuer: overrideIssuer ?? ResolveIssuerOrThrow(),
|
|
audience: overrideAudience ?? ResolveAudienceOrThrow());
|
|
}
|
|
|
|
public static string MintExpired(string secret, string? subject = null)
|
|
{
|
|
return JwtTokenFactory.CreateExpired(
|
|
secret,
|
|
subject ?? DefaultSubject,
|
|
issuer: ResolveIssuerOrThrow(),
|
|
audience: ResolveAudienceOrThrow());
|
|
}
|
|
}
|