feat(auth.apikeys): add IApiKeyAdminStore.SetScopesAsync + SetEnabledAsync (editable scopes + reversible enable, no schema change); bump 0.1.3

This commit is contained in:
Joseph Doherty
2026-06-02 03:08:19 -04:00
parent 30c60f9d5f
commit 468959ca8a
7 changed files with 271 additions and 3 deletions
@@ -292,6 +292,59 @@ public sealed class ApiKeyAdminCommandsTests : IAsyncLifetime
Assert.Equal(auditCountBefore, auditCountAfter);
}
// --- set-scopes / enable-disable ---
[Fact]
public async Task SetEnabledAsync_And_SetScopesAsync_AppendAuditEntries()
{
ApiKeyAdminCommands commands = BuildCommands();
await commands.InitDbAsync(null, CancellationToken.None);
await commands.CreateKeyAsync(
"key-1",
"Service A",
new HashSet<string>(["read"], StringComparer.Ordinal),
null,
null,
CancellationToken.None);
// Disable, then re-enable, then replace scopes.
KeyActionResult disabled =
await commands.SetEnabledAsync("key-1", enabled: false, "10.0.0.1", CancellationToken.None);
Assert.True(disabled.Succeeded);
Assert.Null(await _read.FindActiveByKeyIdAsync("key-1", CancellationToken.None));
KeyActionResult enabled =
await commands.SetEnabledAsync("key-1", enabled: true, "10.0.0.1", CancellationToken.None);
Assert.True(enabled.Succeeded);
Assert.NotNull(await _read.FindActiveByKeyIdAsync("key-1", CancellationToken.None));
KeyActionResult scoped = await commands.SetScopesAsync(
"key-1",
new HashSet<string>(["read", "write"], StringComparer.Ordinal),
"10.0.0.1",
CancellationToken.None);
Assert.True(scoped.Succeeded);
IReadOnlyList<ApiKeyAuditEntry> recent = await _audit.ListRecentAsync(50, CancellationToken.None);
Assert.Single(recent, e => e.EventType == "disable-key");
Assert.Single(recent, e => e.EventType == "enable-key");
Assert.Single(recent, e => e.EventType == "set-scopes");
IReadOnlyList<ApiKeyListItem> listed = await commands.ListKeysAsync(CancellationToken.None);
ApiKeyListItem item = Assert.Single(listed, k => k.KeyId == "key-1");
Assert.True(item.Scopes.SetEquals(new HashSet<string>(["read", "write"], StringComparer.Ordinal)));
}
[Fact]
public async Task SetScopesAsync_NullScopes_Throws()
{
ApiKeyAdminCommands commands = BuildCommands();
await commands.InitDbAsync(null, CancellationToken.None);
await Assert.ThrowsAnyAsync<ArgumentException>(() =>
commands.SetScopesAsync("key-1", null!, null, CancellationToken.None));
}
// --- delete-key ---
[Fact]