V 2.2.1 Anmeldung per Passkey (WebAuthn)

Passkey-Login analog zu werte-next: auf der Login-Seite "Mit Passkey
anmelden", Verwaltung der eigenen Passkeys im neuen Tab Einstellungen.
Das Passwort bleibt als Alternative bestehen.

- lib/webauthn.ts, lib/passkeys.ts sowie API-Routen unter /api/passkey
- proxy.ts: /api/passkey/authenticate ohne Session erreichbar
- TabLayout: dritter Tab Einstellungen (Index-Tabs statt Route wie werte)
- RP-Konfiguration ueber AUSGABEN_RP_*, da sich die .env auf dem Server
  mit anderen Apps teilt, die RP_ID/RP_ORIGIN bereits belegen
- Eigene Tabelle ausgaben_passkeys aus demselben Grund: werte-next nutzt
  in der gemeinsamen Datenbank RXF bereits die Tabelle passkeys
- eslint.config.mjs auf die Flat-Config von eslint-config-next umgestellt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-09 16:35:35 +02:00
parent de28922784
commit d3718da1f8
21 changed files with 1070 additions and 21 deletions
+15 -2
View File
@@ -4,7 +4,8 @@ import { useState, useEffect } from 'react';
import AusgabenForm from '@/components/AusgabenForm';
import AusgabenList from '@/components/AusgabenList';
import MonatsStatistik from '@/components/MonatsStatistik';
import TabLayout from '@/components/TabLayout';
import TabLayout, { SETTINGS_TAB } from '@/components/TabLayout';
import Passkeys from '@/components/Passkeys';
import { AusgabenEntry } from '@/types/ausgaben';
const MAX_ENTRIES = 15;
@@ -17,8 +18,9 @@ export default function Home() {
const [statsRefreshKey, setStatsRefreshKey] = useState(0);
useEffect(() => {
fetchRecentEntries();
setSelectedEntry(null); // Clear selected entry when switching tabs
if (activeTab === SETTINGS_TAB) return; // Einstellungen laden keine Einträge
fetchRecentEntries();
}, [activeTab]);
const fetchRecentEntries = async () => {
@@ -60,6 +62,17 @@ export default function Home() {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
if (activeTab === SETTINGS_TAB) {
return (
<TabLayout activeTab={activeTab} onTabChange={setActiveTab}>
<div>
<h2 className="text-xl font-semibold mb-4">Einstellungen</h2>
<Passkeys />
</div>
</TabLayout>
);
}
return (
<TabLayout activeTab={activeTab} onTabChange={setActiveTab}>
<div>