using System.Net; using System.Net.Http.Headers; using NLog; using Polly; using Polly.Retry; using StabilityMatrix.Core.Helper; namespace StabilityMatrix.Core.Api; public class TokenAuthHeaderHandler : DelegatingHandler { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly AsyncRetryPolicy policy; private readonly ITokenProvider tokenProvider; public TokenAuthHeaderHandler(ITokenProvider tokenProvider) { this.tokenProvider = tokenProvider; policy = Policy .HandleResult( r => r.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden && r.RequestMessage?.Headers.Authorization is { Scheme: "Bearer", Parameter: not null } ) .RetryAsync( async (result, _) => { var oldToken = ObjectHash.GetStringSignature( await tokenProvider.GetAccessTokenAsync().ConfigureAwait(false) ); Logger.Info( "Refreshing access token for status ({StatusCode})", result.Result.StatusCode ); var (newToken, _) = await tokenProvider .RefreshTokensAsync() .ConfigureAwait(false); Logger.Info( "Access token refreshed: {OldToken} -> {NewToken}", ObjectHash.GetStringSignature(oldToken), ObjectHash.GetStringSignature(newToken) ); } ); // InnerHandler must be left as null when using DI, but must be assigned a value when // using RestService.For // InnerHandler = new HttpClientHandler(); } protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) { return policy.ExecuteAsync(async () => { var accessToken = await tokenProvider.GetAccessTokenAsync().ConfigureAwait(false); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); }); } }