Dashboard: delete revoked API keys + confirm Rotate/Revoke/Delete

Add IApiKeyAdminStore.DeleteAsync that only deletes already-revoked
rows (active keys must be revoked first so the revoke event lands in
the audit log before the row disappears) and a matching admin-gated
DashboardApiKeyManagementService.DeleteAsync. ApiKeysPage now shows
Delete on revoked rows in place of the old "No actions" stub, and
Rotate/Revoke/Delete all route through ConfirmDialog so each
destructive action requires an explicit confirmation step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-24 07:30:30 -04:00
parent c5153d68bb
commit 24cc5fd0f0
7 changed files with 218 additions and 15 deletions
@@ -143,6 +143,39 @@ public sealed class DashboardApiKeyManagementService(
}
}
public async Task<DashboardApiKeyManagementResult> DeleteAsync(
ClaimsPrincipal user,
string keyId,
CancellationToken cancellationToken)
{
if (!CanManage(user))
{
return DashboardApiKeyManagementResult.Fail(UnauthorizedMessage);
}
string? validation = ValidateKeyId(keyId);
if (validation is not null)
{
return DashboardApiKeyManagementResult.Fail(validation);
}
string normalizedKeyId = keyId.Trim();
bool deleted = await adminStore
.DeleteAsync(normalizedKeyId, cancellationToken)
.ConfigureAwait(false);
await AppendAuditAsync(
normalizedKeyId,
"dashboard-delete-key",
deleted ? "deleted" : "not-found-or-active",
cancellationToken)
.ConfigureAwait(false);
return deleted
? DashboardApiKeyManagementResult.Success("API key deleted.")
: DashboardApiKeyManagementResult.Fail("API key was not found, or is still active. Revoke it before deleting.");
}
private async Task AppendAuditAsync(
string? keyId,
string eventType,