• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / HashGate / 31638793105

12 Aug 2026 08:40PM UTC coverage: 66.008% (+0.03%) from 65.982%
31638793105

push

github

pwelter34
fix package issue

236 of 474 branches covered (49.79%)

Branch coverage included in aggregate %.

700 of 944 relevant lines covered (74.15%)

26.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.0
/src/HashGate.HttpClient/HmacAuthenticationHttpHandler.cs
1
using System.Net.Http;
2

3
using Microsoft.Extensions.DependencyInjection;
4
using Microsoft.Extensions.Options;
5

6
namespace HashGate.HttpClient;
7

8
/// <summary>
9
/// An HTTP message handler that automatically adds HMAC authentication headers to outgoing HTTP requests.
10
/// This handler integrates with the .NET HTTP client pipeline to transparently sign requests using HMAC-SHA256.
11
/// </summary>
12
/// <remarks>
13
/// <para>
14
/// The handler automatically adds the following headers to requests that don't already have an Authorization header:
15
/// </para>
16
/// <list type="bullet">
17
/// <item><description><c>x-timestamp</c> - Current Unix timestamp</description></item>
18
/// <item><description><c>x-content-sha256</c> - Base64-encoded SHA256 hash of the request body</description></item>
19
/// <item><description><c>Authorization</c> - HMAC authentication header with client ID, signed headers, and signature</description></item>
20
/// </list>
21
/// <para>
22
/// This handler should be registered in the dependency injection container and used with HttpClient instances
23
/// that need to authenticate using HMAC. It respects existing Authorization headers and will not overwrite them.
24
/// </para>
25
/// </remarks>
26
/// <example>
27
/// <para>Register and use with HttpClient:</para>
28
/// <code>
29
/// // Register the handler
30
/// services.AddHmacAuthentication(options =>
31
/// {
32
///     options.Client = "my-client-id";
33
///     options.Secret = "my-secret-key";
34
/// });
35
///
36
/// // Use with HttpClient
37
/// services.AddHttpClient("ApiClient")
38
///     .AddHttpMessageHandler&lt;HmacAuthenticationHttpHandler&gt;();
39
///
40
/// // The handler will automatically sign all requests made through this client
41
/// var client = httpClientFactory.CreateClient("ApiClient");
42
/// var response = await client.GetAsync("https://api.example.com/data");
43
/// </code>
44
/// </example>
45
public class HmacAuthenticationHttpHandler : DelegatingHandler
46
{
47
    /// <summary>
48
    /// A placeholder base address used to indicate that the actual base address should be applied later.
49
    /// </summary>
50
    internal static readonly Uri DeferredBaseAddress = new("http://hashgate.invalid/");
1✔
51

52
    private readonly Func<HmacAuthenticationOptions> _getOptions;
53

54
    /// <summary>
55
    /// Initializes a new instance of the <see cref="HmacAuthenticationHttpHandler"/> class.
56
    /// </summary>
57
    /// <param name="options">The HMAC authentication options containing client credentials and configuration.</param>
58
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> is <c>null</c>.</exception>
59
    [Obsolete("Use HmacAuthenticationHttpHandler(IOptionsMonitor<HmacAuthenticationOptions>, string?) instead.")]
60
    public HmacAuthenticationHttpHandler(IOptions<HmacAuthenticationOptions> options)
1✔
61
    {
62
        if (options is null)
1!
63
            throw new ArgumentNullException(nameof(options), "Options cannot be null.");
×
64

65
        _getOptions = () => options.Value;
2✔
66
    }
1✔
67

68
    /// <summary>
69
    /// Initializes a new instance of the <see cref="HmacAuthenticationHttpHandler"/> class.
70
    /// </summary>
71
    /// <param name="optionsMonitor">The monitored HMAC authentication options containing client credentials and configuration.</param>
72
    /// <param name="optionsName">The named options instance to use, or <c>null</c> to use the default options instance.</param>
73
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="optionsMonitor"/> is <c>null</c>.</exception>
74
    [ActivatorUtilitiesConstructor]
75
    public HmacAuthenticationHttpHandler(IOptionsMonitor<HmacAuthenticationOptions> optionsMonitor, string? optionsName = null)
12✔
76
    {
77
        if (optionsMonitor is null)
12!
78
            throw new ArgumentNullException(nameof(optionsMonitor), "Options monitor cannot be null.");
×
79

80
        // CurrentValue is equivalent to Get(Options.DefaultName), so the default and named
81
        // cases collapse into a single Get(...) call at request time.
82
        var resolvedOptionsName = optionsName ?? Microsoft.Extensions.Options.Options.DefaultName;
12✔
83
        _getOptions = () => optionsMonitor.Get(resolvedOptionsName);
23✔
84
    }
12✔
85

86
    /// <summary>
87
    /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation.
88
    /// If the request does not already contain an Authorization header, HMAC authentication headers are automatically added.
89
    /// </summary>
90
    /// <param name="request">The HTTP request message to send to the server.</param>
91
    /// <param name="cancellationToken">A cancellation token to cancel operation.</param>
92
    /// <returns>
93
    /// A task that represents the asynchronous operation. The task result contains the HTTP response message.
94
    /// </returns>
95
    /// <remarks>
96
    /// <para>
97
    /// This method checks if the request already has an Authorization header. If not, it calls the
98
    /// <see cref="HttpRequestMessageExtensions.AddHmacAuthentication(HttpRequestMessage, HmacAuthenticationOptions, CancellationToken)"/>
99
    /// extension method to add the required HMAC authentication headers including:
100
    /// </para>
101
    /// <list type="bullet">
102
    /// <item><description>Timestamp header for request timing validation</description></item>
103
    /// <item><description>Content hash header for request body integrity</description></item>
104
    /// <item><description>Authorization header with HMAC signature</description></item>
105
    /// </list>
106
    /// <para>
107
    /// The handler preserves any existing Authorization header to allow for manual authentication control
108
    /// or to prevent double-signing of requests.
109
    /// </para>
110
    /// </remarks>
111
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="request"/> is <c>null</c>.</exception>
112
    /// <exception cref="InvalidOperationException">Thrown when HMAC authentication options are invalid or incomplete.</exception>
113
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
114
    {
115
        if (request == null)
12!
116
            throw new ArgumentNullException(nameof(request), "Request cannot be null.");
×
117

118
        var options = _getOptions();
12✔
119
        ApplyBaseAddress(request, options);
12✔
120

121
        // If the request does not already have an Authorization header, add HMAC headers
122
        if (request.Headers.Authorization == null)
12✔
123
            await request.AddHmacAuthentication(options, cancellationToken).ConfigureAwait(false);
12✔
124

125
        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
12✔
126
    }
12✔
127

128
    private static void ApplyBaseAddress(HttpRequestMessage request, HmacAuthenticationOptions options)
129
    {
130
        if (request.RequestUri == null)
12!
131
            return;
×
132

133
        if (options.BaseAddress == null)
12✔
134
        {
135
            // A null options base address is valid when the HttpClient supplies its own base address
136
            // (or the caller uses absolute request URIs); those requests already carry a usable absolute URI.
137
            // Only fail when there is nothing to resolve against: a relative URI, or the unresolved deferred
138
            // placeholder, either of which would otherwise be sent to the invalid placeholder host.
139
            if (!request.RequestUri.IsAbsoluteUri || IsDeferredBaseAddress(request.RequestUri))
3!
140
            {
141
                throw new InvalidOperationException(
×
142
                    "The HMAC authentication base address has not been configured. " +
×
143
                    "Set HmacAuthenticationOptions.BaseAddress, configure the HttpClient base address, or provide an absolute request URI.");
×
144
            }
145

146
            return;
3✔
147
        }
148

149
        if (!request.RequestUri.IsAbsoluteUri)
9!
150
        {
151
            request.RequestUri = new Uri(options.BaseAddress, request.RequestUri);
×
152
            return;
×
153
        }
154

155
        if (IsDeferredBaseAddress(request.RequestUri))
9✔
156
        {
157
            var relativeUri = request.RequestUri.PathAndQuery.TrimStart('/');
1✔
158
            request.RequestUri = new Uri(options.BaseAddress, relativeUri);
1✔
159
        }
160
    }
9✔
161

162
    private static bool IsDeferredBaseAddress(Uri requestUri)
163
        => string.Equals(requestUri.Scheme, DeferredBaseAddress.Scheme, StringComparison.OrdinalIgnoreCase)
12✔
164
            && string.Equals(requestUri.Host, DeferredBaseAddress.Host, StringComparison.OrdinalIgnoreCase)
12✔
165
            && requestUri.Port == DeferredBaseAddress.Port;
12✔
166
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc