feat(auth): ScadaBridge CentralUI pages onto IInboundApiKeyAdmin seam (re-arch C3; string keyId, method-scopes replace ApprovedApiKeyIds, token-once display, approved-keys<->scopes inversion)
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
@page "/admin/api-keys"
|
||||
@using ZB.MOM.WW.ScadaBridge.Security
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security
|
||||
@attribute [Authorize(Policy = AuthorizationPolicies.RequireAdmin)]
|
||||
@inject IInboundApiRepository InboundApiRepository
|
||||
@inject IInboundApiKeyAdmin ApiKeyAdmin
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IDialogService Dialog
|
||||
|
||||
@@ -44,29 +43,29 @@
|
||||
<table class="table table-sm table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Key ID</th>
|
||||
<th>Name</th>
|
||||
<th>Key Hash</th>
|
||||
<th>Methods</th>
|
||||
<th style="width: 160px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var key in FilteredKeys)
|
||||
{
|
||||
<tr @key="key.Id">
|
||||
<td>@key.Id</td>
|
||||
<tr @key="key.KeyId">
|
||||
<td><code>@TruncateKeyId(key.KeyId)</code></td>
|
||||
<td>
|
||||
@key.Name
|
||||
@if (!key.IsEnabled)
|
||||
@if (!key.Enabled)
|
||||
{
|
||||
<span class="badge bg-secondary ms-1">Disabled</span>
|
||||
}
|
||||
</td>
|
||||
<td><code>@MaskKeyValue(key.KeyHash)</code></td>
|
||||
<td>@key.Methods.Count</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-outline-primary btn-sm py-0 px-2"
|
||||
@onclick='() => NavigationManager.NavigateTo($"/admin/api-keys/{key.Id}/edit")'>Edit</button>
|
||||
@onclick='() => NavigationManager.NavigateTo($"/admin/api-keys/{key.KeyId}/edit")'>Edit</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary btn-sm py-0 px-2"
|
||||
data-bs-toggle="dropdown"
|
||||
@@ -75,7 +74,7 @@
|
||||
<li>
|
||||
<button class="dropdown-item"
|
||||
@onclick="() => ToggleKey(key)">
|
||||
@(key.IsEnabled ? "Disable" : "Enable")
|
||||
@(key.Enabled ? "Disable" : "Enable")
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider" /></li>
|
||||
@@ -98,14 +97,17 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<ApiKey> _keys = new();
|
||||
// Inbound-API key re-arch (C3): this page reads keys from the IInboundApiKeyAdmin seam
|
||||
// (string KeyId, method-scopes) rather than the SQL Server ApiKey entity. The seam has no
|
||||
// retrievable hash, so the old masked Key-Hash column is gone; KeyId identifies each row.
|
||||
private List<InboundApiKeyInfo> _keys = new();
|
||||
private bool _loading = true;
|
||||
private string? _errorMessage;
|
||||
private string _search = string.Empty;
|
||||
|
||||
private ToastNotification _toast = default!;
|
||||
|
||||
private IEnumerable<ApiKey> FilteredKeys =>
|
||||
private IEnumerable<InboundApiKeyInfo> FilteredKeys =>
|
||||
string.IsNullOrWhiteSpace(_search)
|
||||
? _keys
|
||||
: _keys.Where(k =>
|
||||
@@ -122,7 +124,7 @@
|
||||
_errorMessage = null;
|
||||
try
|
||||
{
|
||||
_keys = (await InboundApiRepository.GetAllApiKeysAsync()).ToList();
|
||||
_keys = (await ApiKeyAdmin.ListAsync()).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -131,20 +133,22 @@
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private static string MaskKeyValue(string keyValue)
|
||||
// Show a short, recognizable prefix of the opaque KeyId rather than the full 32-char value.
|
||||
private static string TruncateKeyId(string keyId)
|
||||
{
|
||||
if (keyValue.Length <= 8) return new string('*', keyValue.Length);
|
||||
return keyValue[..4] + new string('*', keyValue.Length - 8) + keyValue[^4..];
|
||||
if (string.IsNullOrEmpty(keyId)) return keyId;
|
||||
return keyId.Length <= 12 ? keyId : keyId[..12] + "…";
|
||||
}
|
||||
|
||||
private async Task ToggleKey(ApiKey key)
|
||||
private async Task ToggleKey(InboundApiKeyInfo key)
|
||||
{
|
||||
try
|
||||
{
|
||||
key.IsEnabled = !key.IsEnabled;
|
||||
await InboundApiRepository.UpdateApiKeyAsync(key);
|
||||
await InboundApiRepository.SaveChangesAsync();
|
||||
_toast.ShowSuccess($"API key '{key.Name}' {(key.IsEnabled ? "enabled" : "disabled")}.");
|
||||
var newEnabled = !key.Enabled;
|
||||
// The seam persists; there is no separate SaveChangesAsync.
|
||||
await ApiKeyAdmin.SetEnabledAsync(key.KeyId, newEnabled);
|
||||
_toast.ShowSuccess($"API key '{key.Name}' {(newEnabled ? "enabled" : "disabled")}.");
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -152,7 +156,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteKey(ApiKey key)
|
||||
private async Task DeleteKey(InboundApiKeyInfo key)
|
||||
{
|
||||
var confirmed = await Dialog.ConfirmAsync(
|
||||
"Delete API Key",
|
||||
@@ -162,8 +166,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
await InboundApiRepository.DeleteApiKeyAsync(key.Id);
|
||||
await InboundApiRepository.SaveChangesAsync();
|
||||
await ApiKeyAdmin.DeleteAsync(key.KeyId);
|
||||
_toast.ShowSuccess($"API key '{key.Name}' deleted.");
|
||||
await LoadDataAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user