Heimdall sign-in: pin the public base URL path, the nonce shape and the Discord role requirement; drop a redundant key re-check

The key lookup's second cache check after taking the gate changed nothing the refetch throttle did not
already decide, so it is removed. Mutation testing of the diff found the three behaviours above unpinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 05:25:14 +02:00
commit f5432f7a42
3 changed files with 44 additions and 7 deletions

View file

@ -643,6 +643,32 @@ public sealed class HeimdallCompletionTests
Assert.Null(host.Heimdall.StartSecrets[0]);
}
[Theory]
[InlineData("https://localhost", "https://localhost/auth/heimdall/complete?attempt=")]
[InlineData("https://localhost/", "https://localhost/auth/heimdall/complete?attempt=")]
[InlineData("https://localhost/sub", "https://localhost/sub/auth/heimdall/complete?attempt=")]
[InlineData("https://localhost/sub/", "https://localhost/sub/auth/heimdall/complete?attempt=")]
public async Task The_return_target_Heimdall_gets_is_under_the_public_base_url(string publicBaseUrl, string prefix)
{
using var host = new HeimdallHost(publicBaseUrl: publicBaseUrl);
var attempt = await host.StartAsync();
Assert.Equal(prefix + attempt.Nonce, attempt.ReturnTo);
Assert.True((await Genuine(host, attempt, HeimdallShapes.NewAccountId())).SignedIn);
}
[Fact]
public async Task A_member_start_with_no_allowed_discord_roles_is_refused_before_Heimdall_is_called()
{
using var host = new HeimdallHost(discordRoles: false);
var member = await host.StartAsync(member: true);
Assert.Equal(ErrorRedirect, member.Response.Headers.Location!.OriginalString);
Assert.Empty(host.Heimdall.StartBodies);
Assert.NotNull((await host.StartAsync()).Cookie);
}
[Fact]
public async Task Every_completion_refusal_looks_the_same()
{
@ -674,6 +700,9 @@ public sealed class HeimdallCompletionTests
"Heimdall did not grant Bifrost member access.")
};
var illFormed = await host.CompleteAsync(new string('A', 42) + "!", "code", "x");
results.Add(("ill-formed nonce", illFormed, "Heimdall completion has no attempt for this browser."));
using var client = host.NewClient();
using var json = await client.PostAsync(
"/auth/heimdall/complete",
@ -694,6 +723,7 @@ public sealed class HeimdallCompletionTests
}
Assert.Null(await host.Account(accountId));
Assert.False(illFormed.Response.Headers.Contains("Set-Cookie"));
foreach (var url in new[] { "/auth/heimdall/bogus", "/auth/heimdall/twitch?access=member" })
{

View file

@ -348,6 +348,8 @@ public sealed class HeimdallHost : TestWebApplicationFactory
private readonly string _appSecret;
private readonly string _baseUrl;
private readonly string _publicBaseUrl;
private readonly bool _discordRoles;
public TestClock Clock { get; } = new();
@ -357,10 +359,16 @@ public sealed class HeimdallHost : TestWebApplicationFactory
public HeimdallKey Key { get; } = HeimdallKey.Generate();
public HeimdallHost(string appSecret = AppSecret, string baseUrl = HeimdallShapes.Issuer)
public HeimdallHost(
string appSecret = AppSecret,
string baseUrl = HeimdallShapes.Issuer,
string publicBaseUrl = "https://localhost",
bool discordRoles = true)
{
_appSecret = appSecret;
_baseUrl = baseUrl;
_publicBaseUrl = publicBaseUrl;
_discordRoles = discordRoles;
Heimdall.Published.Add(Key);
Heimdall.RequiredAppSecret = AppSecret;
}
@ -378,8 +386,12 @@ public sealed class HeimdallHost : TestWebApplicationFactory
["Heimdall:BaseUrl"] = _baseUrl,
["Heimdall:AppSecret"] = _appSecret,
["Heimdall:DiscordGuildId"] = "guild-1",
["Heimdall:DiscordAllowedRoleIds:0"] = "role-1"
["Host:PublicBaseUrl"] = _publicBaseUrl
});
if (_discordRoles)
{
configuration.AddInMemoryCollection(new Dictionary<string, string?> { ["Heimdall:DiscordAllowedRoleIds:0"] = "role-1" });
}
});
builder.ConfigureLogging(logging => logging.AddProvider(Logs));
builder.ConfigureServices(services =>