mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 13:11:08 +00:00
2840ccb9b6
ci/woodpecker/push/build-arm Pipeline was successful
This commit transitions the project from Azaion.Flights to Azaion.Missions, updating namespaces, DTOs, services, and database entities accordingly. The Docker configuration and entry points have been modified to reflect the new project structure. Additionally, the README and documentation have been updated to clarify the ongoing renaming process and its implications. All references to flights have been replaced with missions, ensuring consistency across the codebase.
108 lines
5.6 KiB
C#
108 lines
5.6 KiB
C#
using Azaion.Missions.Infrastructure;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.IdentityModel.Protocols;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace Azaion.Missions.Auth;
|
|
|
|
public static class JwtExtensions
|
|
{
|
|
public const string JwtIssuerEnvVar = "JWT_ISSUER";
|
|
public const string JwtIssuerConfigKey = "Jwt:Issuer";
|
|
public const string JwtAudienceEnvVar = "JWT_AUDIENCE";
|
|
public const string JwtAudienceConfigKey = "Jwt:Audience";
|
|
public const string JwtJwksUrlEnvVar = "JWT_JWKS_URL";
|
|
public const string JwtJwksUrlConfigKey = "Jwt:JwksUrl";
|
|
public const string JwtJwksAutoRefreshSecondsEnvVar = "JWT_JWKS_AUTO_REFRESH_INTERVAL_SECONDS";
|
|
public const string JwtJwksAutoRefreshSecondsConfigKey = "Jwt:JwksAutoRefreshIntervalSeconds";
|
|
public const string JwtJwksRefreshSecondsEnvVar = "JWT_JWKS_REFRESH_INTERVAL_SECONDS";
|
|
public const string JwtJwksRefreshSecondsConfigKey = "Jwt:JwksRefreshIntervalSeconds";
|
|
|
|
public static IServiceCollection AddJwtAuth(this IServiceCollection services, IConfiguration configuration)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
ArgumentNullException.ThrowIfNull(configuration);
|
|
|
|
var issuer = ConfigurationResolver.ResolveRequiredOrThrow(configuration, JwtIssuerEnvVar, JwtIssuerConfigKey, "JWT issuer");
|
|
var audience = ConfigurationResolver.ResolveRequiredOrThrow(configuration, JwtAudienceEnvVar, JwtAudienceConfigKey, "JWT audience");
|
|
var jwksUrl = ConfigurationResolver.ResolveRequiredOrThrow(configuration, JwtJwksUrlEnvVar, JwtJwksUrlConfigKey, "JWKS URL");
|
|
|
|
// Optional interval overrides. Production leaves both unset and inherits
|
|
// the library defaults (AutomaticRefreshInterval = 12h, RefreshInterval =
|
|
// 5min). Tests set them to small values so JWKS rotation can be observed
|
|
// inside the CI wall-clock budget.
|
|
var autoRefreshSeconds = ConfigurationResolver.ResolveOptionalPositiveIntOrThrow(
|
|
configuration, JwtJwksAutoRefreshSecondsEnvVar, JwtJwksAutoRefreshSecondsConfigKey,
|
|
"JWKS automatic refresh interval (seconds)");
|
|
var refreshSeconds = ConfigurationResolver.ResolveOptionalPositiveIntOrThrow(
|
|
configuration, JwtJwksRefreshSecondsEnvVar, JwtJwksRefreshSecondsConfigKey,
|
|
"JWKS refresh interval (seconds)");
|
|
|
|
// JwtBearer's stock ConfigurationManager targets the full OIDC discovery
|
|
// document; admin only exposes JWKS, so we wire a JWKS-only retriever.
|
|
// The manager caches the document and refreshes on the default schedule
|
|
// (matches admin's Cache-Control: public, max-age=3600 on /.well-known/jwks.json).
|
|
var jwksConfigManager = new ConfigurationManager<JsonWebKeySet>(
|
|
jwksUrl,
|
|
new JwksRetriever(),
|
|
new HttpDocumentRetriever { RequireHttps = true });
|
|
|
|
if (autoRefreshSeconds is int autoSec)
|
|
jwksConfigManager.AutomaticRefreshInterval = TimeSpan.FromSeconds(autoSec);
|
|
if (refreshSeconds is int refreshSec)
|
|
jwksConfigManager.RefreshInterval = TimeSpan.FromSeconds(refreshSec);
|
|
|
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidIssuer = issuer,
|
|
ValidateAudience = true,
|
|
ValidAudience = audience,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
// Pin algorithms so a token forged with alg=HS256 using the
|
|
// public key as the HMAC secret cannot pass validation.
|
|
ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256],
|
|
RequireSignedTokens = true,
|
|
RequireExpirationTime = true,
|
|
ClockSkew = TimeSpan.FromSeconds(30),
|
|
IssuerSigningKeyResolver = (_, _, kid, _) =>
|
|
{
|
|
var jwks = jwksConfigManager
|
|
.GetConfigurationAsync(CancellationToken.None)
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
|
|
if (string.IsNullOrEmpty(kid))
|
|
return jwks.GetSigningKeys();
|
|
|
|
return jwks.GetSigningKeys().Where(k => k.KeyId == kid);
|
|
}
|
|
};
|
|
});
|
|
|
|
services.AddAuthorizationBuilder()
|
|
.AddPolicy("FL", p => p.RequireClaim("permissions", "FL"));
|
|
|
|
return services;
|
|
}
|
|
|
|
// ConfigurationManager<JsonWebKeySet> needs an IConfigurationRetriever<JsonWebKeySet>.
|
|
// Microsoft ships OpenIdConnectConfigurationRetriever (full discovery doc) but
|
|
// no JWKS-only equivalent, so we implement the minimal version here.
|
|
private sealed class JwksRetriever : IConfigurationRetriever<JsonWebKeySet>
|
|
{
|
|
public async Task<JsonWebKeySet> GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(address);
|
|
ArgumentNullException.ThrowIfNull(retriever);
|
|
|
|
var document = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false);
|
|
return new JsonWebKeySet(document);
|
|
}
|
|
}
|
|
}
|