116 lines
4.6 KiB
C#
116 lines
4.6 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Polly;
|
|
using Polly.Extensions.Http;
|
|
|
|
namespace StellaOps.Auth.Client;
|
|
|
|
/// <summary>
|
|
/// DI helpers for the StellaOps auth client.
|
|
/// </summary>
|
|
public static class ServiceCollectionExtensions
|
|
{
|
|
/// <summary>
|
|
/// Registers the StellaOps auth client with the provided configuration.
|
|
/// </summary>
|
|
public static IServiceCollection AddStellaOpsAuthClient(this IServiceCollection services, Action<StellaOpsAuthClientOptions> configure)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
ArgumentNullException.ThrowIfNull(configure);
|
|
|
|
services.AddOptions<StellaOpsAuthClientOptions>()
|
|
.Configure(configure)
|
|
.PostConfigure(static options => options.Validate());
|
|
|
|
services.TryAddSingleton<IStellaOpsTokenCache, InMemoryTokenCache>();
|
|
|
|
services.AddHttpClient<StellaOpsDiscoveryCache>((provider, client) =>
|
|
{
|
|
var options = provider.GetRequiredService<IOptionsMonitor<StellaOpsAuthClientOptions>>().CurrentValue;
|
|
client.Timeout = options.HttpTimeout;
|
|
}).AddPolicyHandler(static (provider, _) => CreateRetryPolicy(provider));
|
|
|
|
services.AddHttpClient<StellaOpsJwksCache>((provider, client) =>
|
|
{
|
|
var options = provider.GetRequiredService<IOptionsMonitor<StellaOpsAuthClientOptions>>().CurrentValue;
|
|
client.Timeout = options.HttpTimeout;
|
|
}).AddPolicyHandler(static (provider, _) => CreateRetryPolicy(provider));
|
|
|
|
services.AddHttpClient<IStellaOpsTokenClient, StellaOpsTokenClient>((provider, client) =>
|
|
{
|
|
var options = provider.GetRequiredService<IOptionsMonitor<StellaOpsAuthClientOptions>>().CurrentValue;
|
|
client.Timeout = options.HttpTimeout;
|
|
}).AddPolicyHandler(static (provider, _) => CreateRetryPolicy(provider));
|
|
|
|
return services;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a file-backed token cache implementation.
|
|
/// </summary>
|
|
public static IServiceCollection AddStellaOpsFileTokenCache(this IServiceCollection services, string cacheDirectory)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(cacheDirectory);
|
|
|
|
services.Replace(ServiceDescriptor.Singleton<IStellaOpsTokenCache>(provider =>
|
|
{
|
|
var logger = provider.GetService<Microsoft.Extensions.Logging.ILogger<FileTokenCache>>();
|
|
var options = provider.GetRequiredService<IOptionsMonitor<StellaOpsAuthClientOptions>>().CurrentValue;
|
|
return new FileTokenCache(cacheDirectory, TimeProvider.System, options.ExpirationSkew, logger);
|
|
}));
|
|
|
|
return services;
|
|
}
|
|
|
|
private static IAsyncPolicy<HttpResponseMessage> CreateRetryPolicy(IServiceProvider provider)
|
|
{
|
|
var options = provider.GetRequiredService<IOptionsMonitor<StellaOpsAuthClientOptions>>().CurrentValue;
|
|
var delays = options.NormalizedRetryDelays;
|
|
if (delays.Count == 0)
|
|
{
|
|
return Policy.NoOpAsync<HttpResponseMessage>();
|
|
}
|
|
|
|
var logger = provider.GetService<ILoggerFactory>()?.CreateLogger("StellaOps.Auth.Client.HttpRetry");
|
|
|
|
return HttpPolicyExtensions
|
|
.HandleTransientHttpError()
|
|
.OrResult(static response => response.StatusCode == HttpStatusCode.TooManyRequests)
|
|
.WaitAndRetryAsync(
|
|
delays.Count,
|
|
attempt => delays[attempt - 1],
|
|
(outcome, delay, attempt, _) =>
|
|
{
|
|
if (logger is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (outcome.Exception is not null)
|
|
{
|
|
logger.LogWarning(
|
|
outcome.Exception,
|
|
"Retrying Authority HTTP call ({Attempt}/{TotalAttempts}) after exception; waiting {Delay}.",
|
|
attempt,
|
|
delays.Count,
|
|
delay);
|
|
}
|
|
else
|
|
{
|
|
logger.LogWarning(
|
|
"Retrying Authority HTTP call ({Attempt}/{TotalAttempts}) due to status {StatusCode}; waiting {Delay}.",
|
|
attempt,
|
|
delays.Count,
|
|
outcome.Result!.StatusCode,
|
|
delay);
|
|
}
|
|
});
|
|
}
|
|
}
|