API Test
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
@page "/passkeys"
|
||||
@implements IDisposable
|
||||
@using System.Security.Cryptography
|
||||
@implements IDisposable
|
||||
|
||||
<PageTitle>Passkeys Management - Chrono DB</PageTitle>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1 text-white">Passkey Credentials Database (`PasskeyCredential`)</h2>
|
||||
<p class="text-secondary mb-0">Browse, add, edit, and delete WebAuthn / FIDO2 security credentials with raw binary byte arrays.</p>
|
||||
<p class="text-secondary mb-0">Browse, add, edit, and delete WebAuthn / FIDO2 security credentials with raw
|
||||
binary byte arrays.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-chrono-secondary" @onclick="LoadPasskeysAsync">
|
||||
@@ -32,8 +33,9 @@
|
||||
<div class="d-flex align-items-center justify-content-between mb-3 gap-3">
|
||||
<div class="input-group" style="max-width: 400px;">
|
||||
<span class="input-group-text bg-dark border-secondary text-secondary"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control form-control-chrono" placeholder="Search by AaGuid, ID, or SignCount..."
|
||||
@bind="searchTerm" @bind:event="oninput" @bind:after="FilterPasskeys" />
|
||||
<input type="text" class="form-control form-control-chrono"
|
||||
placeholder="Search by AaGuid, ID, or SignCount..."
|
||||
@bind="searchTerm" @bind:event="oninput" @bind:after="FilterPasskeys"/>
|
||||
@if (!string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
<button class="btn btn-outline-secondary" @onclick="ClearSearch"><i class="bi bi-x-lg"></i></button>
|
||||
@@ -73,53 +75,61 @@
|
||||
<div class="table-responsive">
|
||||
<table class="chrono-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 80px;">ID</th>
|
||||
<th style="width: 220px;">AAGUID</th>
|
||||
<th style="width: 120px;">Sign Count</th>
|
||||
<th>Credential ID (Byte[])</th>
|
||||
<th>Public Key (Byte[])</th>
|
||||
<th style="width: 170px;" class="text-end">Actions</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width: 80px;">ID</th>
|
||||
<th style="width: 220px;">AAGUID</th>
|
||||
<th style="width: 120px;">Sign Count</th>
|
||||
<th>Credential ID (Byte[])</th>
|
||||
<th>Public Key (Byte[])</th>
|
||||
<th style="width: 170px;" class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var p in passkeys)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge bg-dark border border-secondary text-white">#@p.Id</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-white small font-monospace">@(string.IsNullOrEmpty(p.AaGuid) ? "(None)" : p.AaGuid)</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-purple">@p.SignCount</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-secondary small font-monospace text-truncate" style="max-width: 200px;" title="@Convert.ToHexString(p.CredentialId)">
|
||||
@Convert.ToHexString(p.CredentialId)[..Math.Min(16, p.CredentialId.Length * 2)]... (@p.CredentialId.Length bytes)
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-secondary small font-monospace text-truncate" style="max-width: 200px;" title="@Convert.ToHexString(p.PublicKey)">
|
||||
@Convert.ToHexString(p.PublicKey)[..Math.Min(16, p.PublicKey.Length * 2)]... (@p.PublicKey.Length bytes)
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-chrono-secondary" @onclick="() => OpenViewModal(p)" title="View Binary Details">
|
||||
<i class="bi bi-eye-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-chrono-secondary" @onclick="() => OpenEditModal(p)" title="Edit Passkey">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-chrono-danger" @onclick="() => OpenDeleteModal(p)" title="Delete Passkey">
|
||||
<i class="bi bi-trash3-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var p in passkeys)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge bg-dark border border-secondary text-white">#@p.Id</span>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="text-white small font-monospace">@(string.IsNullOrEmpty(p.AaGuid) ? "(None)" : p.AaGuid)</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-purple">@p.SignCount</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-secondary small font-monospace text-truncate" style="max-width: 200px;"
|
||||
title="@Convert.ToHexString(p.CredentialId)">
|
||||
@Convert.ToHexString(p.CredentialId)[..Math.Min(16, p.CredentialId.Length * 2)]...
|
||||
(@p.CredentialId.Length bytes)
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-secondary small font-monospace text-truncate" style="max-width: 200px;"
|
||||
title="@Convert.ToHexString(p.PublicKey)">
|
||||
@Convert.ToHexString(p.PublicKey)[..Math.Min(16, p.PublicKey.Length * 2)]...
|
||||
(@p.PublicKey.Length bytes)
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-chrono-secondary" @onclick="() => OpenViewModal(p)"
|
||||
title="View Binary Details">
|
||||
<i class="bi bi-eye-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-chrono-secondary" @onclick="() => OpenEditModal(p)"
|
||||
title="Edit Passkey">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-chrono-danger" @onclick="() => OpenDeleteModal(p)"
|
||||
title="Delete Passkey">
|
||||
<i class="bi bi-trash3-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -151,13 +161,14 @@
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-8">
|
||||
<label class="form-label text-secondary small fw-bold">AAGUID</label>
|
||||
<input type="text" class="form-control form-control-chrono font-monospace" placeholder="e.g. 00000000-0000-0000-0000-000000000000"
|
||||
@bind="currentPasskey.AaGuid" />
|
||||
<input type="text" class="form-control form-control-chrono font-monospace"
|
||||
placeholder="e.g. 00000000-0000-0000-0000-000000000000"
|
||||
@bind="currentPasskey.AaGuid"/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-secondary small fw-bold">SIGN COUNT</label>
|
||||
<input type="number" class="form-control form-control-chrono" min="0"
|
||||
@bind="currentPasskey.SignCount" />
|
||||
@bind="currentPasskey.SignCount"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -188,7 +199,7 @@
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-chrono font-monospace"
|
||||
placeholder="e.g. DEADBEAF01020304"
|
||||
@bind="userHandleHex" />
|
||||
@bind="userHandleHex"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer-custom">
|
||||
@@ -254,7 +265,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer-custom">
|
||||
<button type="button" class="btn btn-chrono-secondary" @onclick="() => showViewModal = false">Close</button>
|
||||
<button type="button" class="btn btn-chrono-secondary" @onclick="() => showViewModal = false">Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -270,17 +282,20 @@
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
Confirm Delete Passkey
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" @onclick="() => showDeleteModal = false"></button>
|
||||
<button type="button" class="btn-close btn-close-white"
|
||||
@onclick="() => showDeleteModal = false"></button>
|
||||
</div>
|
||||
<div class="modal-body-custom">
|
||||
<p class="text-white mb-2">Are you sure you want to delete passkey credential:</p>
|
||||
<div class="p-2 rounded bg-dark border border-secondary fw-bold text-primary mb-3 font-monospace">
|
||||
Passkey #@passkeyToDelete.Id (AAGUID: @(passkeyToDelete.AaGuid ?? "N/A"))
|
||||
</div>
|
||||
<p class="text-secondary small mb-0">This will permanently delete the binary security credential from the database.</p>
|
||||
<p class="text-secondary small mb-0">This will permanently delete the binary security credential from
|
||||
the database.</p>
|
||||
</div>
|
||||
<div class="modal-footer-custom">
|
||||
<button type="button" class="btn btn-chrono-secondary" @onclick="() => showDeleteModal = false">Cancel</button>
|
||||
<button type="button" class="btn btn-chrono-secondary" @onclick="() => showDeleteModal = false">Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-chrono-danger" @onclick="ConfirmDeleteAsync" disabled="@isSaving">
|
||||
@(isSaving ? "Deleting..." : "Delete Passkey")
|
||||
</button>
|
||||
@@ -293,18 +308,20 @@
|
||||
private List<PasskeyCredential> passkeys = [];
|
||||
private string searchTerm = "";
|
||||
private bool isLoading = true;
|
||||
private bool isSaving = false;
|
||||
private bool isSaving;
|
||||
private string? alertMessage;
|
||||
private bool alertIsSuccess;
|
||||
|
||||
private bool showModal;
|
||||
private bool isEditing;
|
||||
|
||||
private PasskeyCredential currentPasskey = new()
|
||||
{
|
||||
CredentialId = [],
|
||||
PublicKey = [],
|
||||
UserHandle = []
|
||||
};
|
||||
|
||||
private string credIdHex = "";
|
||||
private string pubKeyHex = "";
|
||||
private string userHandleHex = "";
|
||||
@@ -429,12 +446,14 @@
|
||||
alertMessage = "Invalid Credential ID hex string (must be even length).";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cleanPub) || cleanPub.Length % 2 != 0)
|
||||
{
|
||||
alertIsSuccess = false;
|
||||
alertMessage = "Invalid Public Key hex string (must be even length).";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cleanUser) || cleanUser.Length % 2 != 0)
|
||||
{
|
||||
alertIsSuccess = false;
|
||||
@@ -506,4 +525,5 @@
|
||||
{
|
||||
ConfigService.OnConfigurationChanged -= HandleConfigChanged;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user