feat(auth): UX improvements for passkeys, 2FA, and password management

1. Passkey Conditional UI: autocomplete="username webauthn" on email
   field enables browser passkey suggestions in autofill dropdown
2. Trust Device checkbox: "Diesem Gerät 30 Tage vertrauen" option
   during 2FA verification (uses Better Auth trust_device cookie)
3. Local QR code generation: replaced external api.qrserver.com with
   local qrcode package for 2FA setup (no external dependency)
4. SecurityOnboarding component: post-registration wizard suggesting
   passkey setup to new users
5. ChangePassword component: reusable password change form with
   validation, visibility toggles, and changePassword() in authService

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-26 21:15:09 +01:00
parent 69aa837898
commit 7073756117
8 changed files with 903 additions and 208 deletions

View file

@ -1,4 +1,5 @@
<script lang="ts">
import QRCode from 'qrcode';
import type { AuthResult } from '../types';
export interface TwoFactorSetupTranslations {
@ -94,6 +95,15 @@
let backupCodes = $state<string[]>([]);
let copied = $state(false);
let codesCopied = $state(false);
let qrCodeDataUrl = $state('');
$effect(() => {
if (totpURI) {
QRCode.toDataURL(totpURI, { width: 200, margin: 2 }).then((url: string) => {
qrCodeDataUrl = url;
});
}
});
/** Extract the secret from an otpauth:// URI */
function extractSecret(uri: string): string {
@ -299,15 +309,17 @@
{#if totpURI}
<div class="qr-section">
<img
src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={encodeURIComponent(
totpURI
)}"
alt="QR Code for TOTP setup"
width="200"
height="200"
class="qr-image"
/>
{#if qrCodeDataUrl}
<img
src={qrCodeDataUrl}
alt="QR Code for TOTP setup"
width="200"
height="200"
class="qr-image"
/>
{:else}
<div class="qr-placeholder" style:width="200px" style:height="200px"></div>
{/if}
</div>
<div class="manual-entry">
@ -605,6 +617,14 @@
padding: 0.5rem;
}
.qr-placeholder {
border-radius: 0.5rem;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.manual-entry {
margin-top: 0.75rem;
}