Heimdall start refusal: log the HTTP status and Heimdall's error code
The journal said only 'Heimdall rejected the auth start', which cannot tell a wrong app secret (401 app_auth_required) from a fault. The message now carries the status and, when the body has one, an error code that is a short snake_case token; no other body text is echoed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fe0ff40864
commit
c085cead9d
2 changed files with 50 additions and 2 deletions
|
|
@ -53,6 +53,32 @@ public sealed class HeimdallAuthService(
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Names the HTTP status and, when the body carries one, Heimdall's error code. The code is shown only
|
||||
/// if it is a short snake_case token, so nothing else from the body (free text, tokens) reaches a log.
|
||||
/// </summary>
|
||||
private static async Task<string> RefusedStartMessageAsync(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
var detail = $"HTTP {(int)response.StatusCode}";
|
||||
try
|
||||
{
|
||||
var body = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken);
|
||||
if (body.ValueKind == JsonValueKind.Object
|
||||
&& body.TryGetProperty("error", out var error)
|
||||
&& error.ValueKind == JsonValueKind.String
|
||||
&& error.GetString() is { Length: > 0 and <= 64 } code
|
||||
&& code.All(c => c is (>= 'a' and <= 'z') or (>= '0' and <= '9') or '_'))
|
||||
{
|
||||
detail += ", " + code;
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
return $"Heimdall rejected the auth start ({detail}).";
|
||||
}
|
||||
|
||||
public async Task<HeimdallAuthStartResult> StartAsync(
|
||||
string provider,
|
||||
string callbackBaseUrl,
|
||||
|
|
@ -91,7 +117,7 @@ public sealed class HeimdallAuthService(
|
|||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new HeimdallAuthException("Heimdall rejected the auth start.");
|
||||
throw new HeimdallAuthException(await RefusedStartMessageAsync(response, cancellationToken));
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<HeimdallStartResponse>(cancellationToken);
|
||||
|
|
|
|||
|
|
@ -1115,7 +1115,7 @@ public sealed class HeimdallCompletionTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.InternalServerError, "{}", "Heimdall rejected the auth start.")]
|
||||
[InlineData(HttpStatusCode.InternalServerError, "{}", "Heimdall rejected the auth start (HTTP 500).")]
|
||||
[InlineData(HttpStatusCode.OK, "{}", "Heimdall auth start returned no authorization URL.")]
|
||||
[InlineData(HttpStatusCode.OK, "{\"authorizationUrl\":\" \"}", "Heimdall auth start returned no authorization URL.")]
|
||||
public async Task A_start_Heimdall_cannot_serve_issues_no_cookie_and_a_fixed_refusal(HttpStatusCode status, string body, string reason)
|
||||
|
|
@ -1132,6 +1132,28 @@ public sealed class HeimdallCompletionTests
|
|||
Assert.Contains("Heimdall start refused: " + reason, string.Join("\n", host.Logs.Lines));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.Unauthorized, """{"error":"app_auth_required"}""", "Heimdall rejected the auth start (HTTP 401, app_auth_required).")]
|
||||
[InlineData(HttpStatusCode.BadRequest, """{"error":"provider_not_supported_for_app","detail":"free text"}""", "Heimdall rejected the auth start (HTTP 400, provider_not_supported_for_app).")]
|
||||
[InlineData(HttpStatusCode.Unauthorized, """{"error":"<b>Not a code</b> CANARY secret=abc"}""", "Heimdall rejected the auth start (HTTP 401).")]
|
||||
[InlineData(HttpStatusCode.Unauthorized, """{"error":{"nested":"app_auth_required"}}""", "Heimdall rejected the auth start (HTTP 401).")]
|
||||
[InlineData(HttpStatusCode.Unauthorized, "not json at all", "Heimdall rejected the auth start (HTTP 401).")]
|
||||
public async Task A_refused_start_logs_the_status_and_a_well_formed_error_code_and_nothing_else_from_the_body(
|
||||
HttpStatusCode status, string body, string reason)
|
||||
{
|
||||
using var host = new HeimdallHost();
|
||||
host.Heimdall.StartStatus = status;
|
||||
host.Heimdall.StartBody = body;
|
||||
|
||||
var attempt = await host.StartAsync(member: true);
|
||||
|
||||
Assert.Equal(ErrorRedirect, attempt.Response.Headers.Location!.OriginalString);
|
||||
var logs = string.Join("\n", host.Logs.Lines);
|
||||
Assert.Contains("Heimdall start refused: " + reason, logs);
|
||||
Assert.DoesNotContain("CANARY", logs);
|
||||
Assert.DoesNotContain("free text", logs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Refusals_echo_no_input_in_responses_or_logs()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue