Initial commit (history squashed)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build Test Deploy / authority-container (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / docs (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / deploy (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / build-test (push) Has been cancelled
				
			
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build Test Deploy / authority-container (push) Has been cancelled
				
			Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Build Test Deploy / build-test (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -0,0 +1,115 @@ | ||||
| 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); | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user