141 lines
4.7 KiB
Plaintext
141 lines
4.7 KiB
Plaintext
@namespace ZB.MOM.WW.Secrets.Ui.Components
|
|
@implements IDisposable
|
|
@inject ISecretStore Store
|
|
@inject IAuditWriter Audit
|
|
@inject IServiceProvider Services
|
|
@* Components/ConfirmDeleteModal.razor — an in-page Blazor overlay (NOT a JS confirm/alert dialog).
|
|
Shown while Visible is true; Confirm tombstones + audits the delete, Cancel dismisses. *@
|
|
|
|
@if (Visible)
|
|
{
|
|
<div class="modal-backdrop-inpage" data-testid="delete-modal-backdrop"></div>
|
|
<div class="modal" role="dialog" aria-modal="true" data-testid="delete-modal">
|
|
<div class="modal-card">
|
|
<h3 class="modal-title">Delete secret '@Name.Value'?</h3>
|
|
<p>This tombstones the secret so the removal can propagate. The action is audited.</p>
|
|
|
|
@if (_error is not null)
|
|
{
|
|
<p class="field-error s-bad" data-testid="delete-error">@_error</p>
|
|
}
|
|
|
|
<div class="btn-row">
|
|
<TechButton Variant="ButtonVariant.Danger" Busy="_busy"
|
|
data-testid="confirm-delete" @onclick="ConfirmAsync">
|
|
Delete
|
|
</TechButton>
|
|
<TechButton Variant="ButtonVariant.Secondary"
|
|
data-testid="cancel-delete" @onclick="CancelAsync">
|
|
Cancel
|
|
</TechButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private readonly CancellationTokenSource _cts = new();
|
|
private string? _error;
|
|
private bool _busy;
|
|
|
|
/// <summary>Whether the modal overlay is rendered.</summary>
|
|
[Parameter] public bool Visible { get; set; }
|
|
|
|
/// <summary>The secret the modal offers to delete.</summary>
|
|
[Parameter] public SecretName Name { get; set; }
|
|
|
|
/// <summary>Raised after the secret has been tombstoned and audited.</summary>
|
|
[Parameter] public EventCallback OnDeleted { get; set; }
|
|
|
|
/// <summary>Raised when the user cancels the deletion.</summary>
|
|
[Parameter] public EventCallback OnCancelled { get; set; }
|
|
|
|
/// <summary>The cascading authentication state, used as the fallback source of the audit actor.</summary>
|
|
[CascadingParameter] private Task<AuthenticationState>? AuthState { get; set; }
|
|
|
|
private async Task ConfirmAsync()
|
|
{
|
|
_error = null;
|
|
_busy = true;
|
|
bool deleted = false;
|
|
try
|
|
{
|
|
string actor = await SecretActorResolver.ResolveAsync(Services, AuthState);
|
|
|
|
try
|
|
{
|
|
await Store.DeleteAsync(Name, actor, _cts.Token);
|
|
|
|
await Audit.WriteAsync(
|
|
new AuditEvent
|
|
{
|
|
EventId = Guid.NewGuid(),
|
|
OccurredAtUtc = DateTimeOffset.UtcNow,
|
|
Actor = actor,
|
|
Action = "secret.delete",
|
|
Outcome = AuditOutcome.Success,
|
|
Category = "Secrets",
|
|
Target = Name.Value,
|
|
},
|
|
_cts.Token);
|
|
|
|
deleted = true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// An unexpected store fault must still be audited as a Failure and surfaced to the
|
|
// operator instead of tearing down the circuit.
|
|
_error = "Unable to delete the secret.";
|
|
await WriteFailureAuditAsync(actor);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_busy = false;
|
|
}
|
|
|
|
if (deleted)
|
|
{
|
|
// Evict any cached plaintext so the ${secret:} / ISecretResolver path does not serve a
|
|
// stale value after this delete for the remainder of its TTL. Optional seam — a no-op
|
|
// when the host has not registered an invalidator.
|
|
Services.GetService<ISecretCacheInvalidator>()?.Invalidate(Name);
|
|
|
|
await OnDeleted.InvokeAsync();
|
|
}
|
|
}
|
|
|
|
private async Task WriteFailureAuditAsync(string actor)
|
|
{
|
|
try
|
|
{
|
|
await Audit.WriteAsync(
|
|
new AuditEvent
|
|
{
|
|
EventId = Guid.NewGuid(),
|
|
OccurredAtUtc = DateTimeOffset.UtcNow,
|
|
Actor = actor,
|
|
Action = "secret.delete",
|
|
Outcome = AuditOutcome.Failure,
|
|
Category = "Secrets",
|
|
Target = Name.Value,
|
|
DetailsJson = "{\"reason\":\"error\"}",
|
|
},
|
|
_cts.Token);
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort audit of a failure; never let audit itself surface into the event pipeline.
|
|
}
|
|
}
|
|
|
|
private Task CancelAsync() => OnCancelled.InvokeAsync();
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
_cts.Cancel();
|
|
_cts.Dispose();
|
|
}
|
|
}
|