diff --git a/examples/Passwordless.AspNetIdentity.Example/.gitignore b/examples/Passwordless.AspNetIdentity.Example/.gitignore new file mode 100644 index 0000000..4e25221 --- /dev/null +++ b/examples/Passwordless.AspNetIdentity.Example/.gitignore @@ -0,0 +1,2 @@ +example.db +example.db-* \ No newline at end of file diff --git a/examples/Passwordless.AspNetIdentity.Example/appsettings.json b/examples/Passwordless.AspNetIdentity.Example/appsettings.json index d9ad6f2..64b0436 100644 --- a/examples/Passwordless.AspNetIdentity.Example/appsettings.json +++ b/examples/Passwordless.AspNetIdentity.Example/appsettings.json @@ -7,8 +7,8 @@ }, "AllowedHosts": "*", "Passwordless": { - "ApiKey": "YOUR_API_SECRET", - "ApiSecret": "YOUR_API_KEY", + "ApiKey": "YOUR_API_KEY", + "ApiSecret": "YOUR_API_SECRET", "Register": { "Discoverable": true } diff --git a/src/Passwordless/Helpers/Extensions/HttpExtensions.cs b/src/Passwordless/Helpers/Extensions/HttpExtensions.cs new file mode 100644 index 0000000..8e93e93 --- /dev/null +++ b/src/Passwordless/Helpers/Extensions/HttpExtensions.cs @@ -0,0 +1,47 @@ +using System.Net; + +namespace Passwordless.Helpers.Extensions; + +internal static class HttpExtensions +{ + private class NonDisposableHttpContent : HttpContent + { + private readonly HttpContent _content; + + public NonDisposableHttpContent(HttpContent content) => _content = content; + + protected override async Task SerializeToStreamAsync( + Stream stream, + TransportContext? context + ) => await _content.CopyToAsync(stream); + + protected override bool TryComputeLength(out long length) + { + length = default; + return false; + } + } + + public static HttpRequestMessage Clone(this HttpRequestMessage request) + { + var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri) + { + Version = request.Version, + // Don't dispose the original request's content + Content = request.Content is not null + ? new NonDisposableHttpContent(request.Content) + : null + }; + + foreach (var header in request.Headers) + clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + + if (request.Content is not null && clonedRequest.Content is not null) + { + foreach (var header in request.Content.Headers) + clonedRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return clonedRequest; + } +} \ No newline at end of file diff --git a/src/Passwordless/PasswordlessHttpHandler.cs b/src/Passwordless/PasswordlessHttpHandler.cs index fe05c1b..52ad65b 100644 --- a/src/Passwordless/PasswordlessHttpHandler.cs +++ b/src/Passwordless/PasswordlessHttpHandler.cs @@ -1,5 +1,6 @@ using System.Net.Http.Json; using Passwordless.Helpers; +using Passwordless.Helpers.Extensions; namespace Passwordless; @@ -16,10 +17,18 @@ public PasswordlessHttpHandler(HttpClient http, bool disposeClient = false) } protected override async Task SendAsync( - HttpRequestMessage request, + HttpRequestMessage providedRequest, CancellationToken cancellationToken) { - var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + // Clone the request to reset its completion status, which is required + // because we're crossing the boundary between two HTTP clients. + using var request = providedRequest.Clone(); + + var response = await _http.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken + ); // On failed requests, check if responded with ProblemDetails and provide a nicer error if so if (!request.ShouldSkipErrorHandling() && diff --git a/src/Passwordless/PasswordlessHttpRequestExtensions.cs b/src/Passwordless/PasswordlessHttpRequestExtensions.cs index a3aaaa7..c4c028b 100644 --- a/src/Passwordless/PasswordlessHttpRequestExtensions.cs +++ b/src/Passwordless/PasswordlessHttpRequestExtensions.cs @@ -24,8 +24,7 @@ internal static bool ShouldSkipErrorHandling(this HttpRequestMessage request) return request.Options.TryGetValue(SkipErrorHandlingOption, out var doNotErrorHandle) && doNotErrorHandle; #elif NET462 || NETSTANDARD2_0 return request.Properties.TryGetValue(SkipErrorHandlingOption, out var shouldSkipOptionObject) - && shouldSkipOptionObject is bool shouldSkipOption - && shouldSkipOption; + && shouldSkipOptionObject is true; #endif } } \ No newline at end of file