Show passkey names and dates, add individual delete
Deploy Dashboard / deploy (push) Successful in 31s
Deploy Dashboard / deploy (push) Successful in 31s
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
let error = $state("");
|
||||
let saving = $state(false);
|
||||
|
||||
let passkeyCount = $state(0);
|
||||
let passkeys = $state([]);
|
||||
let passkeyMsg = $state("");
|
||||
let passkeyErr = $state("");
|
||||
let passkeyLoading = $state(false);
|
||||
@@ -19,7 +19,7 @@
|
||||
async function loadPasskeys() {
|
||||
try {
|
||||
const res = await get("/auth/passkey/list");
|
||||
passkeyCount = res.count;
|
||||
passkeys = res.passkeys;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -50,10 +50,12 @@
|
||||
opts.excludeCredentials = opts.excludeCredentials.map(c => ({ ...c, id: base64urlToBuffer(c.id) }));
|
||||
}
|
||||
const cred = await navigator.credentials.create({ publicKey: opts });
|
||||
const name = prompt("Name this passkey (e.g. MacBook, iPhone):", "Passkey") || "Passkey";
|
||||
const body = {
|
||||
id: cred.id,
|
||||
rawId: bufferToBase64url(cred.rawId),
|
||||
type: cred.type,
|
||||
name,
|
||||
response: {
|
||||
attestationObject: bufferToBase64url(cred.response.attestationObject),
|
||||
clientDataJSON: bufferToBase64url(cred.response.clientDataJSON),
|
||||
@@ -75,13 +77,27 @@
|
||||
try {
|
||||
await post("/auth/passkey/clear");
|
||||
passkeyMsg = "All passkeys removed";
|
||||
passkeyCount = 0;
|
||||
passkeys = [];
|
||||
} catch (e) {
|
||||
passkeyErr = e.body?.detail || "Failed to remove passkeys";
|
||||
}
|
||||
passkeyLoading = false;
|
||||
}
|
||||
|
||||
async function deletePasskey(id, name) {
|
||||
if (!confirm(`Remove passkey "${name}"?`)) return;
|
||||
passkeyLoading = true;
|
||||
passkeyMsg = ""; passkeyErr = "";
|
||||
try {
|
||||
await post("/auth/passkey/delete", { id });
|
||||
passkeyMsg = `Passkey "${name}" removed`;
|
||||
await loadPasskeys();
|
||||
} catch (e) {
|
||||
passkeyErr = e.body?.detail || "Failed to remove passkey";
|
||||
}
|
||||
passkeyLoading = false;
|
||||
}
|
||||
|
||||
async function changePassword() {
|
||||
error = "";
|
||||
message = "";
|
||||
@@ -144,13 +160,29 @@
|
||||
<div class="text-sm text-rose-600 bg-rose-50 px-3 py-2 rounded-lg mb-4">{passkeyErr}</div>
|
||||
{/if}
|
||||
|
||||
<p class="text-sm text-surface-500 mb-4">{passkeyCount} passkey{passkeyCount !== 1 ? 's' : ''} registered</p>
|
||||
{#if passkeys.length > 0}
|
||||
<div class="space-y-2 mb-4">
|
||||
{#each passkeys as pk}
|
||||
<div class="flex items-center justify-between px-3 py-2 bg-surface-50 rounded-lg">
|
||||
<div>
|
||||
<span class="text-sm font-medium text-surface-700">{pk.name}</span>
|
||||
{#if pk.created_at}
|
||||
<span class="text-xs text-surface-400 ml-2">{new Date(pk.created_at).toLocaleDateString()}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button onclick={() => deletePasskey(pk.id, pk.name)} disabled={passkeyLoading} class="text-xs text-rose-500 hover:text-rose-700 disabled:opacity-50">Remove</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm text-surface-400 mb-4">No passkeys registered</p>
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button onclick={registerPasskey} disabled={passkeyLoading} class="px-4 py-2 text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 rounded-lg transition-colors disabled:opacity-50">
|
||||
Register new passkey
|
||||
</button>
|
||||
{#if passkeyCount > 0}
|
||||
{#if passkeys.length > 1}
|
||||
<button onclick={clearPasskeys} disabled={passkeyLoading} class="px-4 py-2 text-sm font-medium text-rose-600 bg-rose-50 hover:bg-rose-100 rounded-lg transition-colors disabled:opacity-50">
|
||||
Remove all
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user