From c4d55209a46184254e7b5699acd5584d6563dca4 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 26 Mar 2026 10:49:57 +0100 Subject: [PATCH] feat(auth): add PasskeyManager component and production config - PasskeyManager.svelte: reusable component for listing, registering, renaming, and deleting passkeys (German defaults, fully translatable) - Production env: WEBAUTHN_RP_ID=mana.how and WEBAUTHN_ORIGINS for all *.mana.how subdomains in docker-compose.macmini.yml - Local DB: passkeys table created via direct SQL Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.macmini.yml | 3 + .../src/components/PasskeyManager.svelte | 859 ++++++++++++++++++ packages/shared-auth-ui/src/index.ts | 2 + 3 files changed, 864 insertions(+) create mode 100644 packages/shared-auth-ui/src/components/PasskeyManager.svelte diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index dc6f737a6..764f138e7 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -229,6 +229,9 @@ services: STORAGE_BACKEND_URL: http://storage-backend:3035 ADMIN_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY} MANA_LLM_URL: http://mana-llm:3025 + # WebAuthn / Passkeys + WEBAUTHN_RP_ID: mana.how + WEBAUTHN_ORIGINS: https://mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://manadeck.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://playground.mana.how,https://presi.mana.how,https://questions.mana.how,https://skilltree.mana.how,https://storage.mana.how,https://todo.mana.how,https://zitare.mana.how volumes: - analytics_data:/data/analytics ports: diff --git a/packages/shared-auth-ui/src/components/PasskeyManager.svelte b/packages/shared-auth-ui/src/components/PasskeyManager.svelte new file mode 100644 index 000000000..8ce567246 --- /dev/null +++ b/packages/shared-auth-ui/src/components/PasskeyManager.svelte @@ -0,0 +1,859 @@ + + +
+

{t.title}

+ + {#if !passkeyAvailable} + + {:else} + {#if error} + + {/if} + + {#if passkeys.length === 0} +

{t.noPasskeys}

+ {:else} +
    + {#each passkeys as passkey (passkey.id)} +
  • + {#if deletingId === passkey.id} +
    +

    {t.confirmDeleteTitle}

    +

    {t.confirmDeleteMessage}

    +
    + + +
    +
    + {:else if editingId === passkey.id} +
    +
    + { + if (e.key === 'Enter') saveRename(passkey.id); + if (e.key === 'Escape') cancelRename(); + }} + /> +
    +
    + + +
    +
    + {:else} +
    + {getDeviceIcon(passkey.deviceType)} +
    + {getDisplayName(passkey)} + + {t.created}: {formatDate(passkey.createdAt)} + + + {t.lastUsed}: {formatRelativeTime(passkey.lastUsedAt)} + + {#if passkey.backedUp} + {t.backedUp} + {:else} + {t.notBackedUp} + {/if} +
    +
    +
    + + +
    + {/if} +
  • + {/each} +
+ {/if} + + {#if showRegisterForm} +
+

{t.registerTitle}

+ + { + if (e.key === 'Enter') handleRegister(); + if (e.key === 'Escape') cancelRegister(); + }} + /> +
+ + +
+
+ {:else} + + {/if} + {/if} +
+ + diff --git a/packages/shared-auth-ui/src/index.ts b/packages/shared-auth-ui/src/index.ts index 4299de571..ffa5f6e2f 100644 --- a/packages/shared-auth-ui/src/index.ts +++ b/packages/shared-auth-ui/src/index.ts @@ -8,6 +8,7 @@ export { default as GuestWelcomeModal } from './components/GuestWelcomeModal.sve export { default as AuthGateModal } from './components/AuthGateModal.svelte'; export { default as SessionExpiredBanner } from './components/SessionExpiredBanner.svelte'; export { default as AuthGate } from './components/AuthGate.svelte'; +export { default as PasskeyManager } from './components/PasskeyManager.svelte'; // Utilities export { @@ -26,3 +27,4 @@ export type { AuthGateAction, AuthGateTranslations, } from './types'; +export type { PasskeyManagerTranslations } from './components/PasskeyManager.svelte';