@page "/passkeys" @implements IDisposable @using System.Security.Cryptography Passkeys Management - Chrono DB

Passkey Credentials Database (`PasskeyCredential`)

Browse, add, edit, and delete WebAuthn / FIDO2 security credentials with raw binary byte arrays.

@if (!string.IsNullOrEmpty(alertMessage)) { }
@if (!string.IsNullOrEmpty(searchTerm)) { }
Showing @passkeys.Count credential(s)
@if (isLoading) {
Loading passkeys from database...
} else if (!passkeys.Any()) {
No Passkeys Found

No passkey credentials were found in the database.

} else {
@foreach (var p in passkeys) { }
ID AAGUID Sign Count Credential ID (Byte[]) Public Key (Byte[]) Actions
#@p.Id
@(string.IsNullOrEmpty(p.AaGuid) ? "(None)" : p.AaGuid)
@p.SignCount
@Convert.ToHexString(p.CredentialId)[..Math.Min(16, p.CredentialId.Length * 2)]... (@p.CredentialId.Length bytes)
@Convert.ToHexString(p.PublicKey)[..Math.Min(16, p.PublicKey.Length * 2)]... (@p.PublicKey.Length bytes)
}
@if (showModal) { } @if (showViewModal && viewedPasskey != null) { } @if (showDeleteModal && passkeyToDelete != null) { } @code { private List passkeys = []; private string searchTerm = ""; private bool isLoading = true; private bool isSaving = false; 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 = ""; private bool showViewModal; private PasskeyCredential? viewedPasskey; private bool showDeleteModal; private PasskeyCredential? passkeyToDelete; protected override async Task OnInitializedAsync() { ConfigService.OnConfigurationChanged += HandleConfigChanged; await LoadPasskeysAsync(); } private void HandleConfigChanged() { _ = LoadPasskeysAsync(); } private async Task LoadPasskeysAsync() { isLoading = true; try { passkeys = await DbService.GetPasskeysAsync(searchTerm); } catch (Exception ex) { alertIsSuccess = false; alertMessage = "Error loading passkeys: " + ex.Message; } finally { isLoading = false; } } private async Task FilterPasskeys() { passkeys = await DbService.GetPasskeysAsync(searchTerm); } private async Task ClearSearch() { searchTerm = ""; await LoadPasskeysAsync(); } private void OpenAddModal() { isEditing = false; currentPasskey = new PasskeyCredential { AaGuid = Guid.NewGuid().ToString(), SignCount = 0, CredentialId = [], PublicKey = [], UserHandle = [] }; GenerateRandomPasskey(); showModal = true; } private void GenerateRandomPasskey() { var cred = new byte[16]; var pub = new byte[32]; var user = new byte[8]; RandomNumberGenerator.Fill(cred); RandomNumberGenerator.Fill(pub); RandomNumberGenerator.Fill(user); credIdHex = Convert.ToHexString(cred); pubKeyHex = Convert.ToHexString(pub); userHandleHex = Convert.ToHexString(user); currentPasskey.AaGuid = Guid.NewGuid().ToString(); currentPasskey.SignCount = Random.Shared.Next(1, 100); } private void OpenEditModal(PasskeyCredential passkey) { isEditing = true; currentPasskey = new PasskeyCredential { Id = passkey.Id, AaGuid = passkey.AaGuid, SignCount = passkey.SignCount, CredentialId = passkey.CredentialId, PublicKey = passkey.PublicKey, UserHandle = passkey.UserHandle }; credIdHex = Convert.ToHexString(passkey.CredentialId); pubKeyHex = Convert.ToHexString(passkey.PublicKey); userHandleHex = Convert.ToHexString(passkey.UserHandle); showModal = true; } private void CloseModal() { showModal = false; } private static int GetByteCount(string hex) { var clean = hex.Replace(" ", "").Replace("-", ""); return clean.Length / 2; } private async Task SavePasskeyAsync() { try { var cleanCred = credIdHex.Replace(" ", "").Replace("-", ""); var cleanPub = pubKeyHex.Replace(" ", "").Replace("-", ""); var cleanUser = userHandleHex.Replace(" ", "").Replace("-", ""); if (string.IsNullOrWhiteSpace(cleanCred) || cleanCred.Length % 2 != 0) { alertIsSuccess = false; 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; alertMessage = "Invalid User Handle hex string (must be even length)."; return; } currentPasskey.CredentialId = Convert.FromHexString(cleanCred); currentPasskey.PublicKey = Convert.FromHexString(cleanPub); currentPasskey.UserHandle = Convert.FromHexString(cleanUser); isSaving = true; var result = await DbService.SavePasskeyAsync(currentPasskey, !isEditing); isSaving = false; alertIsSuccess = result.Success; alertMessage = result.Message; if (result.Success) { showModal = false; await LoadPasskeysAsync(); } } catch (Exception ex) { alertIsSuccess = false; alertMessage = "Failed to parse hex or save passkey: " + ex.Message; } } private void OpenViewModal(PasskeyCredential passkey) { viewedPasskey = passkey; showViewModal = true; } private void OpenDeleteModal(PasskeyCredential passkey) { passkeyToDelete = passkey; showDeleteModal = true; } private async Task ConfirmDeleteAsync() { if (passkeyToDelete == null) return; isSaving = true; var result = await DbService.DeletePasskeyAsync(passkeyToDelete.Id); isSaving = false; alertIsSuccess = result.Success; alertMessage = result.Message; showDeleteModal = false; passkeyToDelete = null; await LoadPasskeysAsync(); } private async Task SeedSamplePasskeysAsync() { var result = await DbService.SeedSampleDataAsync(); alertIsSuccess = result.Success; alertMessage = result.Message; await LoadPasskeysAsync(); } public void Dispose() { ConfigService.OnConfigurationChanged -= HandleConfigChanged; } }